sshez 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +18 -0
- data/.rubocop.yml +1171 -0
- data/.travis.yml +9 -2
- data/CHANGELOG.md +5 -1
- data/Gemfile.lock +35 -0
- data/README.md +10 -10
- data/bin/sshez +1 -1
- data/lib/sshez.rb +9 -3
- data/lib/sshez/command.rb +63 -0
- data/lib/sshez/exec.rb +179 -32
- data/lib/sshez/parser.rb +128 -0
- data/lib/sshez/printing_manager.rb +52 -0
- data/lib/sshez/runner.rb +60 -0
- data/lib/sshez/version.rb +4 -1
- data/spec/sshez_spec.rb +70 -23
- data/sshez.gemspec +1 -1
- metadata +11 -6
- data/lib/sshez/config_file.rb +0 -83
- data/lib/sshez/params.rb +0 -64
data/.travis.yml
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
-
|
4
|
-
|
3
|
+
- 1.9.3
|
4
|
+
- jruby-18mode # JRuby in 1.8 mode
|
5
|
+
- jruby-19mode # JRuby in 1.9 mode
|
6
|
+
- rbx-2.1.1
|
7
|
+
- 1.8.7
|
8
|
+
- ree
|
9
|
+
addons:
|
10
|
+
code_climate:
|
11
|
+
repo_token: bea2159f1a186adf8b3477b2e84b2048d8abdc96e5350e42f14cec2774a1e2cc
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sshez (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.5)
|
10
|
+
rake (10.4.2)
|
11
|
+
rspec (3.4.0)
|
12
|
+
rspec-core (~> 3.4.0)
|
13
|
+
rspec-expectations (~> 3.4.0)
|
14
|
+
rspec-mocks (~> 3.4.0)
|
15
|
+
rspec-core (3.4.1)
|
16
|
+
rspec-support (~> 3.4.0)
|
17
|
+
rspec-expectations (3.4.0)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.4.0)
|
20
|
+
rspec-mocks (3.4.0)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.4.0)
|
23
|
+
rspec-support (3.4.1)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler (~> 1.10)
|
30
|
+
rake (~> 10.0)
|
31
|
+
rspec (~> 3.4)
|
32
|
+
sshez!
|
33
|
+
|
34
|
+
BUNDLED WITH
|
35
|
+
1.11.2
|
data/README.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Sshez
|
2
|
+
[![Dependency Status](https://gemnasium.com/GomaaK/sshez.svg)](https://gemnasium.com/GomaaK/sshez)
|
2
3
|
[![Gem Version](https://badge.fury.io/rb/sshez.svg)](https://badge.fury.io/rb/sshez)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/GomaaK/sshez/badges/gpa.svg)](https://codeclimate.com/github/GomaaK/sshez)
|
5
|
+
[![Inline docs](http://inch-ci.org/github/GomaaK/sshez.svg?branch=master)](http://inch-ci.org/github/GomaaK/sshez)
|
6
|
+
[![security](https://hakiri.io/github/GomaaK/sshez/master.svg)](https://hakiri.io/github/GomaaK/sshez/master)
|
7
|
+
![](http://ruby-gem-downloads-badge.herokuapp.com/sshez?type=total)
|
3
8
|
|
4
9
|
If you have multiple servers that you access on daily bases! sshez helps you configure your ssh/config file so you will never need to remember ip or ports again.
|
5
10
|
|
@@ -8,7 +13,7 @@ If you have multiple servers that you access on daily bases! sshez helps you con
|
|
8
13
|
Add an alias `mw_server`
|
9
14
|
|
10
15
|
sshez mw_server root@74.125.224.72 -p 120
|
11
|
-
|
16
|
+
|
12
17
|
you will be able to use
|
13
18
|
|
14
19
|
ssh mw_server
|
@@ -22,18 +27,19 @@ you will be able to use
|
|
22
27
|
sshez -h
|
23
28
|
|
24
29
|
Usage:
|
25
|
-
sshez <alias> (role@host
|
30
|
+
sshez add <alias> (role@host) [options]
|
31
|
+
sshez connect <alias>
|
26
32
|
sshez remove <alias>
|
27
33
|
sshez list
|
34
|
+
sshez reset
|
28
35
|
|
29
36
|
Specific options:
|
30
37
|
-p, --port PORT Specify a port
|
31
38
|
-i, --identity_file [key] Add identity
|
32
|
-
-
|
39
|
+
-b, --batch_mode Batch Mode
|
33
40
|
-t, --test Writes nothing
|
34
41
|
|
35
42
|
Common options:
|
36
|
-
-l, --list List aliases
|
37
43
|
-v, --version Show version
|
38
44
|
-h, --help Show this message
|
39
45
|
|
@@ -57,9 +63,3 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
57
63
|
## Missing
|
58
64
|
|
59
65
|
* All the other options in [ssh documentation](http://linux.die.net/man/5/ssh_config)
|
60
|
-
|
61
|
-
## Credit
|
62
|
-
|
63
|
-
Mohamed Osama who gave me this idea
|
64
|
-
|
65
|
-
|
data/bin/sshez
CHANGED
data/lib/sshez.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
-
require 'sshez/
|
2
|
-
require 'sshez/
|
1
|
+
require 'sshez/printing_manager'
|
2
|
+
require 'sshez/parser'
|
3
|
+
require 'sshez/command'
|
3
4
|
require 'sshez/exec'
|
5
|
+
require 'sshez/runner'
|
4
6
|
require 'sshez/version'
|
5
7
|
require 'optparse'
|
6
8
|
require 'ostruct'
|
7
9
|
|
8
|
-
|
10
|
+
#
|
11
|
+
# Main gem module
|
12
|
+
#
|
9
13
|
|
14
|
+
module Sshez
|
15
|
+
# Returns version data
|
10
16
|
def self.version
|
11
17
|
return Sshez::VERSION
|
12
18
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Sshez
|
2
|
+
#
|
3
|
+
# Keeps track of the which command the user called
|
4
|
+
#
|
5
|
+
class Command
|
6
|
+
PRINTER = PrintingManager.instance
|
7
|
+
# Exposes the name and arguments
|
8
|
+
attr_reader :name, :args
|
9
|
+
# no one should create a command except from this class!
|
10
|
+
#
|
11
|
+
# name: can only be one of these [add, remove, list]
|
12
|
+
# validator: a proc that returns true only if the input is valid
|
13
|
+
# for the command
|
14
|
+
# args: the args it self!
|
15
|
+
# correct_format: the way the user should run this command
|
16
|
+
# args_processor: (optional) a proc that will process the args
|
17
|
+
# before setting them
|
18
|
+
#
|
19
|
+
def initialize(name, validator, correct_format, args_processor = nil)
|
20
|
+
@name = name
|
21
|
+
@validator = validator
|
22
|
+
@args = []
|
23
|
+
@correct_format = correct_format
|
24
|
+
@args_processor = args_processor
|
25
|
+
end
|
26
|
+
#
|
27
|
+
# a list of all commands
|
28
|
+
#
|
29
|
+
ALL = {
|
30
|
+
'add' => Command.new('add',
|
31
|
+
(proc { |args| (args.length == 2) && (args[1].include?('@')) }),
|
32
|
+
'sshez add <alias> (role@host) [options]',
|
33
|
+
(proc { |alias_name, role_host| [alias_name] + role_host.split('@') })),
|
34
|
+
'remove' => Command.new('remove', (proc { |args| args.length == 1 }),
|
35
|
+
'sshez remove <alias>'),
|
36
|
+
'connect' => Command.new('connect', (proc { |args| args.length == 1 }),
|
37
|
+
'sshez connect <alias>'),
|
38
|
+
'reset' => Command.new('reset', (proc { |args| args.length == 0 }),
|
39
|
+
'sshez reset'),
|
40
|
+
'list' => Command.new('list', (proc { |args| args.empty? }), 'sshez list')
|
41
|
+
}
|
42
|
+
#
|
43
|
+
# processes the value passed if a processor was defined
|
44
|
+
#
|
45
|
+
def args=(value)
|
46
|
+
@args = @args_processor ? @args_processor.call(value) : value
|
47
|
+
end
|
48
|
+
#
|
49
|
+
# validateds the args using the proc previously defined
|
50
|
+
#
|
51
|
+
def valid?(args)
|
52
|
+
@validator.call(args)
|
53
|
+
end
|
54
|
+
#
|
55
|
+
# returns the text that should appear for a user
|
56
|
+
# in case of invalid input for this command
|
57
|
+
#
|
58
|
+
def error
|
59
|
+
"Invalid input `#{args.join(',')}` for #{@name}.\nUsage: #{@correct_format}"
|
60
|
+
end
|
61
|
+
|
62
|
+
end # class Command
|
63
|
+
end
|
data/lib/sshez/exec.rb
CHANGED
@@ -1,42 +1,189 @@
|
|
1
|
-
|
1
|
+
require 'forwardable'
|
2
|
+
module Sshez
|
3
|
+
#
|
4
|
+
# handles all the ssh commands and updates to the .ssh/config file
|
5
|
+
#
|
2
6
|
class Exec
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
7
|
+
extend Forwardable
|
8
|
+
FILE_PATH = File.expand_path('~') + '/.ssh/config'
|
9
|
+
PRINTER = PrintingManager.instance
|
10
|
+
#
|
11
|
+
# to create an instance pass any +Struct+(listener) that handles the following methods
|
12
|
+
# * :argument_error(+Command+)
|
13
|
+
# * :done_with_no_guarantee
|
14
|
+
# * :permission_error
|
15
|
+
# * :finished_successfully
|
16
|
+
#
|
17
|
+
attr_reader :listener
|
18
|
+
|
19
|
+
def_delegators :@listener, :argument_error
|
20
|
+
def_delegators :@listener, :done_with_no_guarantee
|
21
|
+
|
22
|
+
#
|
23
|
+
# Must have the methods mentioned above
|
24
|
+
#
|
25
|
+
def initialize(listener)
|
26
|
+
@listener = listener
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Starts the execution of the +Command+ parsed with its options
|
31
|
+
#
|
32
|
+
def start_exec(command, options)
|
33
|
+
all_args = command.args
|
34
|
+
all_args << options
|
35
|
+
self.send(command.name, *all_args)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
#
|
40
|
+
# connects to host using alias
|
41
|
+
#
|
42
|
+
def connect(alias_name, options)
|
43
|
+
file = File.open(FILE_PATH, 'r')
|
44
|
+
servers = all_hosts_in(file)
|
45
|
+
if servers.include?alias_name
|
46
|
+
PRINTER.verbose_print "Connecting to #{alias_name}"
|
47
|
+
exec "ssh #{alias_name}"
|
48
|
+
else
|
49
|
+
PRINTER.print "Could not find host `#{alias_name}`"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# append an alias for the given user@host with the options passed
|
55
|
+
#
|
56
|
+
def add(alias_name, user, host, options)
|
57
|
+
begin
|
58
|
+
PRINTER.verbose_print "Adding\n"
|
59
|
+
config_append = form(alias_name, user, host, options)
|
60
|
+
PRINTER.verbose_print config_append
|
61
|
+
unless options.test
|
62
|
+
file = File.open(FILE_PATH, 'a+')
|
63
|
+
file.write(config_append)
|
64
|
+
file.close
|
65
|
+
|
66
|
+
# causes a bug in fedore if permission was not updated to 0600
|
67
|
+
File.chmod(0600, FILE_PATH)
|
68
|
+
# system "chmod 600 #{FILE_PATH}"
|
16
69
|
end
|
70
|
+
rescue
|
71
|
+
return permission_error
|
72
|
+
end
|
73
|
+
PRINTER.verbose_print "to #{FILE_PATH}"
|
74
|
+
PRINTER.print "Successfully added `#{alias_name}` as an alias for `#{user}@#{host}`"
|
75
|
+
PRINTER.print "Try sshez connect #{alias_name}"
|
76
|
+
|
77
|
+
finish_exec
|
78
|
+
end # add(alias_name, user, host, options)
|
79
|
+
|
80
|
+
#
|
81
|
+
# returns the text that will be added to the config file
|
82
|
+
#
|
83
|
+
def form(alias_name, user, host, options)
|
84
|
+
retuned = "\n"
|
85
|
+
retuned += "Host #{alias_name}\n"
|
86
|
+
retuned += " HostName #{host}\n"
|
87
|
+
retuned += " User #{user}\n"
|
88
|
+
|
89
|
+
options.file_content.each_pair do |key, value|
|
90
|
+
retuned += value
|
17
91
|
end
|
92
|
+
retuned
|
18
93
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
94
|
+
end # form(alias_name, user, host, options)
|
95
|
+
|
96
|
+
#
|
97
|
+
# removes an alias from the config file (all its occurrences will be removed too)
|
98
|
+
#
|
99
|
+
def remove(alias_name, options)
|
100
|
+
file = File.open(FILE_PATH, 'r')
|
101
|
+
new_file = File.open(FILE_PATH + 'temp', 'w')
|
102
|
+
remove_alias_name(alias_name, file, new_file)
|
103
|
+
|
104
|
+
File.delete(FILE_PATH)
|
105
|
+
File.rename(FILE_PATH + 'temp', FILE_PATH)
|
106
|
+
# Causes a bug in fedore if permission was not updated to 0600
|
107
|
+
File.chmod(0600, FILE_PATH)
|
108
|
+
|
109
|
+
unless PRINTER.output?
|
110
|
+
PRINTER.print "Could not find host `#{alias_name}`"
|
111
|
+
end
|
112
|
+
finish_exec
|
113
|
+
end # remove(alias_name, options)
|
114
|
+
|
115
|
+
#
|
116
|
+
# copies the content of the file to the new file without
|
117
|
+
# the sections concerning the alias_name
|
118
|
+
#
|
119
|
+
def remove_alias_name(alias_name, file, new_file)
|
120
|
+
started_removing = false
|
121
|
+
file.each do |line|
|
122
|
+
started_removing ||= line.include?("Host #{alias_name}")
|
123
|
+
if started_removing
|
124
|
+
# I will never stop till I find another host that is not the one I'm removing
|
125
|
+
stop_removing = (started_removing && line.include?('Host ') && !(line =~ /\b#{alias_name}\b/))
|
126
|
+
PRINTER.verbose_print line unless stop_removing
|
127
|
+
if stop_removing && started_removing
|
128
|
+
new_file.write(line)
|
129
|
+
end
|
130
|
+
started_removing = !stop_removing
|
131
|
+
else
|
132
|
+
# Everything else should be transfered safely to the other file
|
133
|
+
new_file.write(line)
|
28
134
|
end
|
29
|
-
output = %Q|Invalid input. Use -h for help|
|
30
|
-
puts output
|
31
|
-
return output
|
32
135
|
end
|
33
|
-
|
136
|
+
file.close
|
137
|
+
new_file.close
|
138
|
+
end #remove_alias_name(alias_name, file, new_file)
|
34
139
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
140
|
+
#
|
141
|
+
# lists the aliases available in the config file
|
142
|
+
#
|
143
|
+
def list(options)
|
144
|
+
file = File.open(FILE_PATH, 'a+')
|
145
|
+
servers = all_hosts_in(file)
|
146
|
+
file.close
|
147
|
+
if servers.empty?
|
148
|
+
PRINTER.print 'No aliases added'
|
149
|
+
else
|
150
|
+
PRINTER.print 'Listing aliases:'
|
151
|
+
servers.each{|x| PRINTER.print "\t- #{x}"}
|
39
152
|
end
|
153
|
+
finish_exec
|
154
|
+
end # list(options)
|
155
|
+
|
156
|
+
def reset(options)
|
157
|
+
file = File.open(FILE_PATH, "w")
|
158
|
+
file.close
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Returns all the alias names of in the file
|
163
|
+
#
|
164
|
+
def all_hosts_in(file)
|
165
|
+
servers = []
|
166
|
+
file.each do |line|
|
167
|
+
if line.include?('Host ')
|
168
|
+
servers << line.sub('Host ', '').strip
|
169
|
+
end
|
170
|
+
end
|
171
|
+
servers
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# Raises a permission error to the listener
|
176
|
+
#
|
177
|
+
def permission_error
|
178
|
+
listener.permission_error
|
179
|
+
end
|
180
|
+
|
181
|
+
#
|
182
|
+
# finished editing the file successfully
|
183
|
+
#
|
184
|
+
def finish_exec
|
185
|
+
listener.finished_successfully
|
40
186
|
end
|
41
|
-
|
187
|
+
# private
|
188
|
+
end # class FileManager
|
42
189
|
end
|
data/lib/sshez/parser.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
module Sshez
|
2
|
+
# handles parsing the arguments to meaningful actions
|
3
|
+
#
|
4
|
+
# Parser.new(listener).parse(args)
|
5
|
+
#
|
6
|
+
class Parser
|
7
|
+
PRINTER = PrintingManager.instance
|
8
|
+
#
|
9
|
+
# to create an instance pass any +Struct+ that handles the following methods
|
10
|
+
# * :start_exec(+Command+, +OpenStruct+(options))
|
11
|
+
# * :argument_error(+Command+)
|
12
|
+
# * :done_with_no_guarantee
|
13
|
+
#
|
14
|
+
attr_reader :listener
|
15
|
+
|
16
|
+
#
|
17
|
+
# Must have the methods mentioned above
|
18
|
+
#
|
19
|
+
def initialize(listener)
|
20
|
+
@listener = listener
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Return a structure describing the command and its options.
|
25
|
+
# prints help if no args supplied
|
26
|
+
# command is the first argument passed in the commandline
|
27
|
+
#
|
28
|
+
# The options specified on the command line will be collected in *options*.
|
29
|
+
# options.file_content will contain
|
30
|
+
# what should be added in the next step to the config file
|
31
|
+
def parse(args)
|
32
|
+
args[0] ||= '-h'
|
33
|
+
command = Command::ALL[args.first]
|
34
|
+
options = OpenStruct.new(file_content: OpenStruct.new)
|
35
|
+
init_options_parser(options).parse!(args)
|
36
|
+
args.delete_at(0)
|
37
|
+
return no_command_supplied unless(command && !options.halt)
|
38
|
+
command.args = args
|
39
|
+
return parsing_succeeded(command, options) if command.valid?(args)
|
40
|
+
parsing_failed(command)
|
41
|
+
end # parse(args)
|
42
|
+
|
43
|
+
#
|
44
|
+
# Initates an OptionParser with all of the possible options
|
45
|
+
# and how to handle them
|
46
|
+
#
|
47
|
+
def init_options_parser(options)
|
48
|
+
OptionParser.new do |opts|
|
49
|
+
opts.banner = "Usage:\n"\
|
50
|
+
"\tsshez add <alias> (role@host) [options]\n"\
|
51
|
+
"\tsshez connect <alias>\n"\
|
52
|
+
"\tsshez remove <alias>\n\tsshez list\n"\
|
53
|
+
"\tsshez reset\n"
|
54
|
+
opts.separator ''
|
55
|
+
opts.separator 'Specific options:'
|
56
|
+
options_for_add(opts, options)
|
57
|
+
# signals that we are in testing mode
|
58
|
+
opts.on('-t', '--test', 'Writes nothing') {options.test = true}
|
59
|
+
common_options(opts, options)
|
60
|
+
end # OptionParser.new
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Returns the options specifice to the add command only
|
65
|
+
#
|
66
|
+
def options_for_add(opts, options)
|
67
|
+
opts.on('-p', '--port PORT',
|
68
|
+
'Specify a port') do |port|
|
69
|
+
options.file_content.port_text = " Port #{port}\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on('-i', '--identity_file [key]',
|
73
|
+
'Add identity') do |key_path|
|
74
|
+
options.file_content.identity_file_text =
|
75
|
+
" IdentityFile #{key_path}\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on('-b', '--batch_mode', 'Batch Mode') do
|
79
|
+
options.file_content.batch_mode_text = " BatchMode yes\n"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Returns the standard options
|
85
|
+
#
|
86
|
+
def common_options(opts, options)
|
87
|
+
opts.separator ''
|
88
|
+
opts.separator 'Common options:'
|
89
|
+
# Another typical switch to print the version.
|
90
|
+
opts.on('-v', '--version', 'Show version') do
|
91
|
+
PRINTER.print Sshez.version
|
92
|
+
options.halt = true
|
93
|
+
end
|
94
|
+
opts.on('-z', '--verbose', 'Verbose Output') do
|
95
|
+
PRINTER.verbose!
|
96
|
+
end
|
97
|
+
# Prints everything
|
98
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
99
|
+
PRINTER.print opts
|
100
|
+
options.halt = true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
#
|
107
|
+
# Triggers the listener with the +Command+ and options parsed
|
108
|
+
#
|
109
|
+
def parsing_succeeded(command, options)
|
110
|
+
listener.start_exec(command, options)
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Triggers the listener with the failure of the +Command+
|
115
|
+
#
|
116
|
+
def parsing_failed(command)
|
117
|
+
listener.argument_error(command)
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Handles when there is no command (maybe only an option is given)
|
122
|
+
#
|
123
|
+
def no_command_supplied
|
124
|
+
listener.done_with_no_guarantee
|
125
|
+
end
|
126
|
+
# private
|
127
|
+
end # class Parser
|
128
|
+
end
|