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 +1 -1
- data/README.rdoc +15 -10
- data/completions/_runssh +13 -2
- data/completions/runssh_comp.sh +1 -1
- data/features/scp.feature +59 -0
- data/lib/runsshlib/cli.rb +66 -2
- data/lib/runsshlib/ssh_backend.rb +38 -0
- data/lib/runsshlib/version.rb +2 -3
- data/lib/runsshlib.rb +3 -0
- data/spec/runsshlib/cli_spec.rb +6 -2
- data/spec/runsshlib/ssh_backend_spec.rb +30 -0
- data/spec/support/utils.rb +1 -1
- metadata +7 -5
data/Gemfile.lock
CHANGED
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
|
-
|
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
|
-
|
146
|
-
*
|
147
|
-
|
148
|
-
|
149
|
-
*
|
150
|
-
|
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 \
|
data/completions/runssh_comp.sh
CHANGED
@@ -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
|
data/lib/runsshlib/version.rb
CHANGED
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
|
|
data/spec/runsshlib/cli_spec.rb
CHANGED
@@ -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 '
|
287
|
-
@ab_cli.send(:extract_subcommand, %w(
|
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
|
data/spec/support/utils.rb
CHANGED
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:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
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-
|
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
|