runssh 0.4.3 → 0.5.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/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