fissher 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +39 -0
- data/bin/fissher +9 -0
- data/etc/fissher.conf.sample +14 -0
- data/lib/fissher_base.rb +53 -0
- data/lib/fissher_conf.rb +165 -0
- metadata +115 -0
data/README.rdoc
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
== Fissher
|
3
|
+
|
4
|
+
Fissher is a simple utility to run commands on multiple servers, powered by Net::SSH::Multi.
|
5
|
+
|
6
|
+
=== Usage
|
7
|
+
|
8
|
+
fissher [flags] [command]
|
9
|
+
-G Hostgroup Execute command on all hosts listed in the JSON config for the
|
10
|
+
specified group.
|
11
|
+
-H Host1,Host2 Execute command on hosts listed on the command line
|
12
|
+
-g jumpbox Manually specify/override a jump server, if necessary.
|
13
|
+
-s Execute the provided commands with sudo.
|
14
|
+
-u username Manually specify/override username to connect with.
|
15
|
+
-p Use password based authentication, specified via STDIN
|
16
|
+
-c config.json Manually specify the path to your fissher config file
|
17
|
+
-n num Number of concurrent connections. Enter 0 for unlimited.
|
18
|
+
|
19
|
+
=== Installation
|
20
|
+
|
21
|
+
==== Via Rubygems (Recommended)
|
22
|
+
|
23
|
+
Simply run the following:
|
24
|
+
|
25
|
+
gem install fissher
|
26
|
+
|
27
|
+
==== Manual
|
28
|
+
|
29
|
+
* Place the code into a directory by either unarchiving it or checking it out via git.
|
30
|
+
* Run bundler to install the dependency gems (getopt, net-ssh-multi, net-ssh-session, json, highline)
|
31
|
+
|
32
|
+
=== JSON Config file
|
33
|
+
|
34
|
+
This file, by default, lives in the etc directory one level below where
|
35
|
+
the main script lives. You can specify it manually with the -c option.
|
36
|
+
|
37
|
+
There is a sample configuration file provided with the script that displays
|
38
|
+
all of the currently configurable options. Any variables can be omitted
|
39
|
+
or overridden with command line options.
|
data/bin/fissher
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"user": "user", // Default user name
|
3
|
+
"concurrency": "10", // Set to 0 to disable max concurrency
|
4
|
+
"default_gateway": "jumpbox1.sampledomain.com", // Default jump box, if needed.
|
5
|
+
"hostgroups": {
|
6
|
+
"app": {
|
7
|
+
"gateway": "jumpbox2.sampledomain.com", // Override the default
|
8
|
+
"hosts": ["appsrv01","appsrv02","appsrv03","appsrv04"]
|
9
|
+
},
|
10
|
+
"web": {
|
11
|
+
"hosts": ["websrv01","websrv02"]
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}
|
data/lib/fissher_base.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
$:.unshift File.dirname(__FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'net/ssh/multi'
|
6
|
+
require 'fissher_conf'
|
7
|
+
include FissherConf
|
8
|
+
|
9
|
+
class FissherBase
|
10
|
+
def go_time
|
11
|
+
opts = FissherConf.handle_opts unless !opts.nil?
|
12
|
+
abort "No hosts specified! Please use -H or -G!\n" unless !opts[:hostlist].nil?
|
13
|
+
|
14
|
+
Net::SSH::Multi.start(:concurrent_connections => opts[:concurrency]) do |session|
|
15
|
+
if opts[:gateway]
|
16
|
+
session.via opts[:gateway], opts[:user], :password => opts[:password]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create our connection list
|
20
|
+
opts[:hostlist].each do |host|
|
21
|
+
session.use host, :user => opts[:user], :password => opts[:password]
|
22
|
+
end
|
23
|
+
|
24
|
+
if opts[:command] =~ /^sudo/
|
25
|
+
if opts[:password].nil?
|
26
|
+
p = FissherConf::Misc.new
|
27
|
+
opts[:password] = p.getpass
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get a PTY and exec command on servers
|
31
|
+
session.open_channel do |ch|
|
32
|
+
ch.request_pty do |c, success|
|
33
|
+
raise "could not request pty" unless success
|
34
|
+
ch.exec opts[:command]
|
35
|
+
ch.on_data do |c_, data|
|
36
|
+
if data =~ /\[sudo\]/
|
37
|
+
ch.send_data(opts[:password] + "\n")
|
38
|
+
else
|
39
|
+
puts "[#{ch[:host]}]: #{data}" unless data =~ /^\n$|^\s*$/
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
# Sudo isn't needed. We don't need a PTY.
|
46
|
+
session.exec(opts[:command])
|
47
|
+
end
|
48
|
+
|
49
|
+
# go time... for real.
|
50
|
+
session.loop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/fissher_conf.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
|
2
|
+
#############################################
|
3
|
+
# Option/config parsing for Fissher #
|
4
|
+
#############################################
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
require 'highline/import'
|
8
|
+
require 'getopt/std'
|
9
|
+
|
10
|
+
module FissherConf
|
11
|
+
class Misc
|
12
|
+
def getpass(prompt="Enter remote password: ")
|
13
|
+
ask(prompt) {|q| q.echo = false}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Method for returning hosts from hostgroup.
|
17
|
+
def group_hosts( grp,conf,conf_file )
|
18
|
+
if conf[:hostgroups][:"#{grp}"]
|
19
|
+
conf[:hostgroups][:"#{grp}"][:hosts]
|
20
|
+
else
|
21
|
+
abort "Fatal: hostgroup #{grp} not defined in #{conf_file}!\n"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def usage
|
27
|
+
puts "#{$0} [flags] [command]:\n"
|
28
|
+
puts "-G Hostgroup Execute command on all hosts listed in the JSON config for the\n"
|
29
|
+
puts " specified group.\n"
|
30
|
+
puts "-H Host1,Host2 Execute command on hosts listed on the command line\n"
|
31
|
+
puts "-g jumpbox Manually specify/override a jump server, if necessary.\n"
|
32
|
+
puts "-s Execute the provided commands with sudo."
|
33
|
+
puts "-u username Manually specify/override username to connect with.\n"
|
34
|
+
puts "-p Use password based authentication, specified via STDIN\n"
|
35
|
+
puts "-c config.json Manually specify the path to your fissher config file\n"
|
36
|
+
puts "-n num Number of concurrent connections. Enter 0 for unlimited.\n"
|
37
|
+
puts "-U username Specify an alternate user in conjunction with -s (E.G. -U webmaster)\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
def die( msg )
|
41
|
+
puts "#{msg}\n"
|
42
|
+
usage
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def handle_opts
|
47
|
+
opt = Getopt::Std.getopts("pc:g:G:u:n:sH:hU:")
|
48
|
+
ret = Hash.new
|
49
|
+
|
50
|
+
if opt["h"]
|
51
|
+
usage
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
|
55
|
+
# Import configuration, either from default or a custom JSON config
|
56
|
+
if opt["c"]
|
57
|
+
conf_file = opt["c"]
|
58
|
+
elsif File.exists?("#{Dir.home}/.fissherrc")
|
59
|
+
conf_file = "#{Dir.home}/.fissherrc"
|
60
|
+
elsif File.exists?("/etc/fissher/fissher.conf")
|
61
|
+
conf_file = "/etc/fissher/fissher.conf"
|
62
|
+
else
|
63
|
+
conf_file = File.dirname(__FILE__) + "/../etc/fissher.conf"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Ensure the file exists
|
67
|
+
begin
|
68
|
+
config = JSON.parse(File.read(conf_file),:symbolize_names => true)
|
69
|
+
rescue
|
70
|
+
|
71
|
+
etcloc = File.dirname(__FILE__).to_s.gsub(/\/lib$/, '/etc')
|
72
|
+
puts <<EOB
|
73
|
+
**** It appears that you have not yet created your config file! ****
|
74
|
+
You can do this by copying the sample config to one of the following
|
75
|
+
locations:
|
76
|
+
|
77
|
+
~/.fissherrc
|
78
|
+
/etc/fissher/fissher.conf
|
79
|
+
#{etcloc}/fissher.conf
|
80
|
+
|
81
|
+
You can find a copy of the sample in the following location:
|
82
|
+
|
83
|
+
#{etcloc}
|
84
|
+
|
85
|
+
A script will be included in the next release that will generate the file
|
86
|
+
for you if it does not already exist.
|
87
|
+
EOB
|
88
|
+
abort
|
89
|
+
end
|
90
|
+
|
91
|
+
# Use sudo for our command
|
92
|
+
if opt["s"]
|
93
|
+
if opt["U"]
|
94
|
+
sudo_cmd = "sudo -u #{opt['U']}"
|
95
|
+
else
|
96
|
+
sudo_cmd = "sudo"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Gateway if an edgeserver is present
|
101
|
+
if opt["g"]
|
102
|
+
ret[:gateway] = opt["g"]
|
103
|
+
elsif opt["G"] && !config[:hostgroups][:"#{opt['G']}"][:gateway].nil?
|
104
|
+
ret[:gateway] = config[:hostgroups][:"#{opt['G']}"][:gateway]
|
105
|
+
elsif !config[:default_gateway].nil?
|
106
|
+
ret[:gateway] = config[:default_gateway]
|
107
|
+
end
|
108
|
+
|
109
|
+
# Hostgroup used for batch jobs
|
110
|
+
if opt["G"]
|
111
|
+
abort "You may only specify one of -G or -H!\n" unless opt["H"].nil?
|
112
|
+
h = Misc.new
|
113
|
+
ret[:hostlist] = h.group_hosts(opt["G"],config,conf_file)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Username used by connections
|
117
|
+
if opt["u"]
|
118
|
+
ret[:user] = opt['u']
|
119
|
+
elsif config[:user]
|
120
|
+
ret[:user] = config[:user]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Job Concurrency limit
|
124
|
+
if opt["n"] == '0'
|
125
|
+
ret[:concurrency] = nil
|
126
|
+
elsif opt["n"]
|
127
|
+
ret[:concurrency] = opt["n"].to_i
|
128
|
+
elsif config[:concurrency]
|
129
|
+
ret[:concurrency] = config[:concurrency].to_i
|
130
|
+
else
|
131
|
+
ret[:concurrency] = 10
|
132
|
+
end
|
133
|
+
|
134
|
+
# Host list
|
135
|
+
if opt["H"]
|
136
|
+
abort "You may only specify one of -G or -H!\n" unless opt["G"].nil?
|
137
|
+
if opt["H"] =~ /,/
|
138
|
+
ret[:hostlist] = opt["H"].split(",")
|
139
|
+
else
|
140
|
+
ret[:hostlist] = [ opt["H"] ]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Get our account password
|
145
|
+
if opt["p"]
|
146
|
+
p = Misc.new
|
147
|
+
ret[:password] = p.getpass()
|
148
|
+
else
|
149
|
+
ret[:password] = nil
|
150
|
+
end
|
151
|
+
|
152
|
+
# Our command
|
153
|
+
if ARGV.count >= 1
|
154
|
+
if sudo_cmd
|
155
|
+
ret[:command] = "#{sudo_cmd} " + ARGV.join(' ').to_s
|
156
|
+
else
|
157
|
+
ret[:command] = ARGV.join(' ').to_s
|
158
|
+
end
|
159
|
+
else
|
160
|
+
die "No command specified!\n" unless !ret[:command].nil?
|
161
|
+
end
|
162
|
+
ret
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fissher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jeff Hagadorn
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: net-ssh-multi
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: getopt
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: json
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: highline
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: A utility written to perform batch commands on many servers over SSH.
|
79
|
+
Supports jump servers and hostgroups via a JSON config file.
|
80
|
+
email: jeff@aletheia.io
|
81
|
+
executables:
|
82
|
+
- fissher
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- lib/fissher_conf.rb
|
87
|
+
- lib/fissher_base.rb
|
88
|
+
- etc/fissher.conf.sample
|
89
|
+
- README.rdoc
|
90
|
+
- bin/fissher
|
91
|
+
homepage: http://github.com/dahui/fissher
|
92
|
+
licenses: []
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.8.25
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: Fissher
|
115
|
+
test_files: []
|