fissher 1.0.2
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/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: []
|