sshez 0.3.0 → 1.0.0
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.
- 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
|
+
[](https://gemnasium.com/GomaaK/sshez)
|
2
3
|
[](https://badge.fury.io/rb/sshez)
|
4
|
+
[](https://codeclimate.com/github/GomaaK/sshez)
|
5
|
+
[](http://inch-ci.org/github/GomaaK/sshez)
|
6
|
+
[](https://hakiri.io/github/GomaaK/sshez/master)
|
7
|
+

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