fissher 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+
5
+ require 'fissher_base'
6
+
7
+ fissher = FissherBase.new
8
+
9
+ fissher.go_time
@@ -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
+ }
@@ -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
@@ -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: []