sshez 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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