runssh 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- runssh (0.4.3)
4
+ runssh (0.5.0)
5
5
  highline (~> 1.6.1)
6
6
  trollop (~> 1.16.2)
7
7
 
data/README.rdoc CHANGED
@@ -3,14 +3,13 @@ A CLI utility to bookmark ssh connections with hierarchy and run various
3
3
  ssh commands on these bookmarks. Supported operations are:
4
4
 
5
5
  * Login
6
+ * Scp
6
7
  * Copy ssh key
7
8
  * Run remote command
8
9
  * Create local tunnel
9
10
 
10
- I am planning to add scp support as well.
11
-
12
- This requires you to have the _ssh_ binary in your path and optionally the
13
- <i>ssh-copy-id</i> binary (for copying ssh key).
11
+ This requires you to have the _ssh_ and _scp_ binaries in your path and
12
+ optionally the <i>ssh-copy-id</i> binary (for copying ssh key).
14
13
 
15
14
  == Installation
16
15
  You must have ruby[http://www.ruby-lang.org/] and
@@ -142,10 +141,16 @@ and there's no going back ...)
142
141
  the required option is not given.
143
142
  * Finally - proper zsh completion :)
144
143
 
145
- == TODO
146
- * Create a _proper_ zsh completion script.
147
- * Add scp capabilities.
148
- * Shell via gateway (connect to a firewall and from there open shell to the host).
149
- * Rename (or move) host definition
150
- * Maybe replace invoking ssh from the command line with some library.
144
+ === 0.4.3
145
+ * Added missing options/arguments completions to the _shell_ subcommand.
146
+
147
+ === 0.5.0
148
+ * Added scp support.
149
+
150
+ == TODO for 1.0
151
+ * Merge configurations.
152
+
153
+ == TODO for later
154
+ * Rename (or move) host definition.
151
155
  * Automatic deletion of empty groups.
156
+ * Shell via gateway (connect to a firewall and from there open shell to the host).
data/completions/_runssh CHANGED
@@ -12,14 +12,14 @@ _runssh() {
12
12
  (( $+funtions[_runssh_commands] )) ||
13
13
  _runssh_commands() {
14
14
  if (( CURRENT == 1 )); then
15
- _describe -t commands 'runssh commands' "(shell add del update print import export cpid)" && ret=0
15
+ _describe -t commands 'runssh commands' "(shell scp add del update print import export cpid)" && ret=0
16
16
  else
17
17
  # Since we're using a syntax that clears $words we have to get
18
18
  # the custom config file (if given) somehow.
19
19
  [[ -n "$opt_args[-f]" ]] && runssh_config_file="-f $opt_args[-f]"
20
20
  subcommand=$words[1]
21
21
  case $subcommand in
22
- shell|del|print|import|export|cpid)
22
+ shell|scp|del|print|import|export|cpid)
23
23
  _call_function ret _runssh_subcmd_$subcommand
24
24
  ;;
25
25
  add|update) # they have the same options
@@ -45,6 +45,17 @@ _runssh_subcmd_shell() {
45
45
  '*: :_runssh_path_completions'
46
46
  }
47
47
 
48
+ (( $+functions[_runssh_subcmd_scp] )) ||
49
+ _runssh_subcmd_scp() {
50
+ _arguments \
51
+ '(-L --login)'{-L,--login}'+[Override login]: :' \
52
+ '(-n --host-name)'{-n,--host-name}'+[host to connect to]: :' \
53
+ {\*-o,\*--option}'+[Ssh option]: :' \
54
+ '(-r --recurssive)'{-r,--recurssive}'[copy directories recurssively]' \
55
+ '(-l --limit)'{-l,--limit}'+[limit bandwidth in kbit/s]: :' \
56
+ '*: :_runssh_path_completions'
57
+ }
58
+
48
59
  (( $+functions[_runssh_subcmd_cpid] )) ||
49
60
  _runssh_subcmd_cpid() {
50
61
  _arguments \
@@ -13,7 +13,7 @@ function _runssh () {
13
13
  if [ ${COMP_CWORD} -gt $COM_POSITION ]; then
14
14
  options=$(${COMP_WORDS[@]:0:COMP_CWORD} ? 2>/dev/null)
15
15
  elif [ $COMP_CWORD -eq $COM_POSITION ]; then
16
- options="shell cpid add del update print import export"
16
+ options="shell scp cpid add del update print import export"
17
17
  fi
18
18
  COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
19
19
  return 0
@@ -0,0 +1,59 @@
1
+ Feature: Scp to copy files and directories to and from a remote host
2
+ In order to copy files and directories to/from a bookmarks
3
+ I want to be able to invoke the scp command on these bookmarks.
4
+
5
+ Background:
6
+ Given Bookmark "some host1" exist with:
7
+ | name | value |
8
+ | host-name | some.host |
9
+ Given Bookmark "some host2" exist with:
10
+ | name | value |
11
+ | host-name | some.host2 |
12
+ | login | mylogin |
13
+ Given Bookmark "some host3" exist with:
14
+ | name | value |
15
+ | host-name | some.host3 |
16
+ | login | otherlogin |
17
+ | option | ForwardAgent=true |
18
+
19
+ Scenario: Copy files to server
20
+ When I run the "scp" command with "some host1 -- myfile :remotefile"
21
+ Then It should execute "scp myfile some.host:remotefile"
22
+
23
+ Scenario: Copy files from server
24
+ When I run the "scp" command with "some host1 -- :remotefile myfile"
25
+ Then It should execute "scp some.host:remotefile myfile"
26
+
27
+ Scenario: Copying directories
28
+ When I run the "scp" command with "-r some host1 -- myfile :remotefile"
29
+ Then It should execute "scp -r myfile some.host:remotefile"
30
+
31
+ Scenario: Bookmark with login
32
+ When I run the "scp" command with "some host2 -- :remotefile myfile"
33
+ Then It should execute "scp mylogin@some.host2:remotefile myfile"
34
+
35
+ Scenario: Bookmark with ssh options
36
+ When I run the "scp" command with "some host3 -- myfile :remotefile"
37
+ Then It should execute "scp -o ForwardAgent=true myfile otherlogin@some.host3:remotefile"
38
+
39
+ Scenario: Scp with bandwidth limit
40
+ When I run the "scp" command with "-l 100 some host1 -- :remotefile myfile"
41
+ Then It should execute "scp -l 100 some.host:remotefile myfile"
42
+
43
+ Scenario: scp with full path
44
+ When I run the "scp" command with "some host1 -- :/path/to/remote /path/to/local"
45
+ Then It should execute "scp some.host:/path/to/remote /path/to/local"
46
+
47
+ Scenario: scp without indicating remote file
48
+ When I run the "scp" command with "some host1 -- somefile otherfile"
49
+ Then I should get a "The remote path should be prefixed with" error
50
+
51
+ Scenario: scp with wrong number of arguments
52
+ When I run the "scp" command with "some host1 -- one two three"
53
+ Then I should get a "Invalid targets: one two three" error
54
+
55
+ Scenario: Overriding hostname or login
56
+ When I run the "scp" command with "-n newhost some host2 -- :remote local"
57
+ Then It should execute "scp mylogin@newhost:remote local"
58
+ When I run the "scp" command with "-L otherlogin some host2 -- local :remote"
59
+ Then It should execute "scp local otherlogin@some.host2:remote"
data/lib/runsshlib/cli.rb CHANGED
@@ -20,7 +20,7 @@ require 'trollop'
20
20
  module RunSSHLib
21
21
  class CLI
22
22
 
23
- COMMAND = %w(shell add del update print import export cpid)
23
+ COMMAND = %w(shell add del update print import export cpid scp)
24
24
  MAIN_HELP = <<-EOS
25
25
  Usage: runssh [global_options] COMMAND [options] <path>
26
26
 
@@ -36,6 +36,7 @@ COMMAND : One of the commands mentioned below. It's possible to
36
36
 
37
37
  Available commands:
38
38
  * shell : Open ssh shell on remote host
39
+ * scp : Copy files from/to remote host
39
40
  * add : Add host definition
40
41
  * del : Delete host definition
41
42
  * update : Update host definition
@@ -86,7 +87,7 @@ EOS
86
87
  m = method(command_name.to_sym)
87
88
  m.call(@path)
88
89
  end
89
- rescue ConfigError => e
90
+ rescue ConfigError, ParametersError => e
90
91
  Trollop.die e.message
91
92
  rescue AbortError => e
92
93
  abort e.message
@@ -130,6 +131,8 @@ EOS
130
131
  case cmd
131
132
  when 'shell'
132
133
  parse_shell args
134
+ when 'scp'
135
+ parse_scp args
133
136
  when 'add', 'update'
134
137
  parse_add_update cmd, args
135
138
  when 'del'
@@ -203,6 +206,54 @@ EOS
203
206
  options
204
207
  end
205
208
 
209
+ def parse_scp(args)
210
+ options = Trollop::options(args) do
211
+ banner <<-EOH
212
+ Usage: runssh [global_options] scp [options] <path> -- [:]target [:]target
213
+
214
+ <path> : See main help for description of path.
215
+
216
+ Copy file/directory to/from remote host using scp. The remote target should
217
+ be prefixed with ':'. The paths can be relative or absolute. The remote
218
+ target is extracted to [user@]host:target.
219
+
220
+ Here are some examples to make it clear. Say you have a bookmark named
221
+ "some host" which points to 'some.host' and uses 'root' as login:
222
+
223
+ The command:
224
+ runssh scp some host -- localfile :remotefile
225
+ Is equivalent to:
226
+ scp localfile root@some.host:remotefile
227
+
228
+ The command:
229
+ runssh scp -r some host -- :/path/to/remotedirectory /path/to/localdirectory
230
+ Is equivalent to:
231
+ scp -r root@some.host:/path/to/remotedirectory /path/to/localdirectory
232
+
233
+ Options:
234
+ EOH
235
+ opt :login, "Override the login in the configuration.",
236
+ :type => :string, :short => :L
237
+ opt :host_name, 'Override the name or address of the host.',
238
+ :short => :n, :type => :string
239
+ opt :limit, "Limit the bandwidth. Kbits/s. (Corresponds to scp -l).",
240
+ :short => :l, :type => :string
241
+ opt :recurssive, "Recurssively copy entire directory (corresponds to scp -r)",
242
+ :short => :r
243
+ opt :option, 'Ssh option. Appended to saved ssh options. ' \
244
+ 'Can be used multiple times.',
245
+ :short => :o, :type => :string, :multi => true
246
+ stop_on "--"
247
+ end
248
+ # TODO: Can I find a way to specify targets without -- ?
249
+ if ind = args.index("--")
250
+ targets = args.slice!(ind, args.size - ind)
251
+ targets.delete_at(0)
252
+ options[:targets] = targets
253
+ end
254
+ options
255
+ end
256
+
206
257
  def parse_cpid(args)
207
258
  Trollop::options(args) do
208
259
  banner <<-EOH
@@ -362,6 +413,19 @@ EOS
362
413
  SshBackend.shell(definition)
363
414
  end
364
415
 
416
+ def run_scp(path)
417
+ host = @c.get_host(path)
418
+ definition = host.definition.merge(@options) do |key, this, other|
419
+ case key
420
+ when :option
421
+ this + other
422
+ else
423
+ other ? other : this
424
+ end
425
+ end
426
+ SshBackend.scp(definition)
427
+ end
428
+
365
429
  def run_cpid(path)
366
430
  host = @c.get_host(path)
367
431
  SshBackend.copy_id(host.definition.merge(@options))
@@ -59,6 +59,25 @@ module RunSSHLib
59
59
  exec command
60
60
  end
61
61
 
62
+ # Copy files to/from remote host using scp.
63
+ # definition: A hash containig a merge between host definition and
64
+ # user input.
65
+ def scp(definition)
66
+ raise "no hostname" unless definition[:host_name] # should never happen
67
+ command = "scp"
68
+ arguments = []
69
+ arguments << '-r' if definition[:recurssive_given]
70
+ arguments += ['-l', definition[:limit]] if definition[:limit]
71
+ definition[:option].each do |option|
72
+ arguments += ['-o', option]
73
+ end if definition[:option]
74
+ host_name = definition[:login] ?
75
+ "#{definition[:login]}@#{definition[:host_name]}" :
76
+ definition[:host_name]
77
+ arguments += normalize_scp_targets(definition[:targets], host_name)
78
+ exec command, *arguments
79
+ end
80
+
62
81
  # Copy ssh identity file to remote host according to options
63
82
  # options:: A hash containing host definition (:host_name, etc) and
64
83
  # optionally identity_file path.
@@ -78,5 +97,24 @@ module RunSSHLib
78
97
  tunnel_definition =~ /(^\d+$)/ ? "#{$1}:localhost:#{$1}" :
79
98
  tunnel_definition
80
99
  end
100
+
101
+ # Prepend the remote target with supplied host_name.
102
+ #
103
+ # Raises ParametersError if no target is indicated as remote
104
+ # (with ':') or if the number of targets is not 2.
105
+ def normalize_scp_targets(targets, host_name)
106
+ raise ParametersError, "Invalid targets: #{targets.join(' ')}" unless
107
+ targets.length == 2
108
+ raise "no hostname" unless host_name # should never happen
109
+
110
+ if targets[0].start_with?(':')
111
+ targets[0] = host_name + targets[0]
112
+ elsif targets[1].start_with?(':')
113
+ targets[1] = host_name + targets[1]
114
+ else
115
+ raise ParametersError, "The remote path should be prefixed with ':'!"
116
+ end
117
+ targets
118
+ end
81
119
  end
82
120
  end
@@ -19,10 +19,9 @@
19
19
  module RunSSHLib
20
20
  module Version
21
21
  MAJOR = 0
22
- MINOR = 4
23
- BUILD = 3
22
+ MINOR = 5
23
+ BUILD = 0
24
24
 
25
25
  STRING = [MAJOR, MINOR, BUILD].compact.join('.')
26
26
  end
27
27
  end
28
-
data/lib/runsshlib.rb CHANGED
@@ -38,6 +38,9 @@ module RunSSHLib
38
38
  # message should contain only the older config version!
39
39
  class OlderConfigVersionError < StandardError; end
40
40
 
41
+ # Parameters error
42
+ class ParametersError < StandardError; end
43
+
41
44
  # Just a general abort with error
42
45
  class AbortError < StandardError; end
43
46
 
@@ -283,8 +283,12 @@ describe "The CLI interface" do
283
283
  @ab_cli.send(:extract_subcommand, ['a']).should eql('add')
284
284
  end
285
285
 
286
- it "should parse 's' as shell" do
287
- @ab_cli.send(:extract_subcommand, %w(s root)).should eql('shell')
286
+ it "should parse 'sh' as shell" do
287
+ @ab_cli.send(:extract_subcommand, %w(sh root)).should eql('shell')
288
+ end
289
+
290
+ it "should parse 'sc' as scp" do
291
+ @ab_cli.send(:extract_subcommand, %w(sc root)).should eql('scp')
288
292
  end
289
293
 
290
294
  it "should parse 'i' as import" do
@@ -131,4 +131,34 @@ describe RunSSHLib::SshBackend do
131
131
  should == "7070:localhost:7070"
132
132
  end
133
133
  end
134
+
135
+ describe "#normalize_scp_targets" do
136
+ it "raises exception if not hostname given" do
137
+ expect {
138
+ RunSSHLib::SshBackend.normalize_scp_targets([1, 2], nil)
139
+ }.to raise_error(RuntimeError, /no hostname/)
140
+ end
141
+
142
+ it "raises error if number of targets doesn't match 2" do
143
+ expect {
144
+ RunSSHLib::SshBackend.normalize_scp_targets([1], 'some.host')
145
+ }.to raise_error(RunSSHLib::ParametersError, /Invalid targets/)
146
+ expect {
147
+ RunSSHLib::SshBackend.normalize_scp_targets([1, 2, 3], 'some.host')
148
+ }.to raise_error(RunSSHLib::ParametersError, /Invalid targets/)
149
+ end
150
+
151
+ it "raises an error if none of the targets prefixed with :" do
152
+ expect {
153
+ RunSSHLib::SshBackend.normalize_scp_targets(['one', 'two'], 'host')
154
+ }.to raise_error(RunSSHLib::ParametersError, /should be prefixed/)
155
+ end
156
+
157
+ it "correctly parses targets" do
158
+ RunSSHLib::SshBackend.normalize_scp_targets([':remotefile', 'localfile'],
159
+ 'host').should == ['host:remotefile', 'localfile']
160
+ RunSSHLib::SshBackend.normalize_scp_targets(['/path/to/localfile', ':/path/to/remotefile'],
161
+ 'host').should == ['/path/to/localfile', 'host:/path/to/remotefile']
162
+ end
163
+ end
134
164
  end
@@ -122,7 +122,7 @@ end
122
122
  def stub_ssh_exec
123
123
  RunSSHLib::SshBackend.stub(:exec) do |_command, *args|
124
124
  output = _command
125
- output + args.join(" ") unless args.empty?
125
+ output += (' ' + args.join(" ")) unless args.empty?
126
126
  puts output
127
127
  end
128
128
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runssh
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 3
10
- version: 0.4.3
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Haim Ashkenazi
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-19 00:00:00 +03:00
18
+ date: 2011-05-22 00:00:00 +03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -81,6 +81,7 @@ files:
81
81
  - features/import_bookmarks.feature
82
82
  - features/print_bookmarks.feature
83
83
  - features/run_shell.feature
84
+ - features/scp.feature
84
85
  - features/step_definitions/argument_steps.rb
85
86
  - features/step_definitions/bookmarks_steps.rb
86
87
  - features/step_definitions/output_steps.rb
@@ -150,6 +151,7 @@ test_files:
150
151
  - features/import_bookmarks.feature
151
152
  - features/print_bookmarks.feature
152
153
  - features/run_shell.feature
154
+ - features/scp.feature
153
155
  - features/step_definitions/argument_steps.rb
154
156
  - features/step_definitions/bookmarks_steps.rb
155
157
  - features/step_definitions/output_steps.rb