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.
data/.travis.yml CHANGED
@@ -1,4 +1,11 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.2
4
- before_install: gem install bundler -v 1.10.5
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
@@ -1,4 +1,8 @@
1
- ### Unreleased
1
+ ### 1.0.0 - 2016-02-09
2
+
3
+ * enhancements
4
+ * Added `connect` for ssh connection
5
+ * Added `sshez add` as default and only way to add aliases (breaking change)
2
6
 
3
7
  ### 0.3.0 - 2015-12-14
4
8
 
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|-r) [options]
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
- -r, --remove Remove handle
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
@@ -3,5 +3,5 @@
3
3
  require 'sshez'
4
4
 
5
5
 
6
- worker = Sshez::Exec.new
6
+ worker = Sshez::Runner.new
7
7
  worker.process(ARGV)
data/lib/sshez.rb CHANGED
@@ -1,12 +1,18 @@
1
- require 'sshez/params'
2
- require 'sshez/config_file'
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
- module Sshez
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
- module Sshez
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
- def process(args)
4
- # parse the params to get the options
5
- if args.length == 0
6
- args[0] = '-h'
7
- elsif args[0] == 'list'
8
- return ConfigFile.list
9
- elsif args[0] == 'remove'
10
- if args[1]
11
- return ConfigFile.remove(args[1])
12
- else
13
- output %Q|Select an alias to remove `sshez remove alias`.\n Use `sshez list` to list your aliases|
14
- puts output
15
- return output
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
- if (args.length < 2 || !args[1].include?("@") || args[0][0] == '-')
20
- if args.any? { |x| ['-h', '--help', '-l', '--list'].include?(x) }
21
- Params.parse(args)
22
- return
23
- elsif ['-r', "--remove"].include?(args[1])
24
- return ConfigFile.remove(args[0])
25
- elsif ['-v', '--version'].include?(args[0])
26
- puts Sshez.version
27
- return Sshez.version
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
- options = Params.parse(args)
136
+ file.close
137
+ new_file.close
138
+ end #remove_alias_name(alias_name, file, new_file)
34
139
 
35
- unless options.remove
36
- user, host = args[1].split("@")
37
- output = ConfigFile.append(args[0], user, host, options)
38
- output + "Done!"
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
- end
187
+ # private
188
+ end # class FileManager
42
189
  end
@@ -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