runssh 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +12 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +42 -0
- data/README.rdoc +28 -9
- data/Rakefile +5 -33
- data/autotest/discover.rb +1 -0
- data/bin/runssh +2 -0
- data/lib/runsshlib/cli.rb +79 -32
- data/lib/runsshlib/config_file.rb +61 -13
- data/lib/runsshlib/ssh_backend.rb +15 -10
- data/lib/runsshlib/ssh_host_def.rb +49 -0
- data/lib/runsshlib/version.rb +27 -0
- data/lib/runsshlib.rb +7 -8
- data/runssh.gemspec +37 -0
- data/spec/fixtures/runssh.yml +24 -19
- data/spec/fixtures/runssh_v_none.yml +19 -0
- data/spec/runsshlib/cli_spec.rb +126 -62
- data/spec/runsshlib/config_file_spec.rb +151 -38
- data/spec/runsshlib/ssh_backend_spec.rb +37 -35
- data/spec/runsshlib/ssh_host_def_spec.rb +91 -0
- data/spec/spec_helper.rb +5 -0
- metadata +103 -20
data/.autotest
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'autotest/fsevent'
|
2
|
+
require 'autotest/growl'
|
3
|
+
|
4
|
+
Autotest.add_hook(:initialize) {|at|
|
5
|
+
at.add_exception %r{^\.git} # ignore Version Control System
|
6
|
+
at.add_exception %r{^./tmp} # ignore temp files, lest autotest will run again, and again...
|
7
|
+
# at.clear_mappings # take out the default (test/test*rb)
|
8
|
+
at.add_mapping(%r{^lib/.*\.rb$}) {|f, _|
|
9
|
+
Dir['spec/**/*.rb']
|
10
|
+
}
|
11
|
+
nil
|
12
|
+
}
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
runssh (0.2.0.rc.1)
|
5
|
+
highline (~> 1.6.1)
|
6
|
+
trollop (~> 1.16.2)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://rubygems.org/
|
10
|
+
specs:
|
11
|
+
ZenTest (4.4.2)
|
12
|
+
autotest (4.4.6)
|
13
|
+
ZenTest (>= 4.4.1)
|
14
|
+
autotest-fsevent (0.2.4)
|
15
|
+
sys-uname
|
16
|
+
autotest-growl (0.2.9)
|
17
|
+
diff-lcs (1.1.2)
|
18
|
+
highline (1.6.1)
|
19
|
+
rcov (0.9.9)
|
20
|
+
rspec (2.1.0)
|
21
|
+
rspec-core (~> 2.1.0)
|
22
|
+
rspec-expectations (~> 2.1.0)
|
23
|
+
rspec-mocks (~> 2.1.0)
|
24
|
+
rspec-core (2.1.0)
|
25
|
+
rspec-expectations (2.1.0)
|
26
|
+
diff-lcs (~> 1.1.2)
|
27
|
+
rspec-mocks (2.1.0)
|
28
|
+
sys-uname (0.8.5)
|
29
|
+
trollop (1.16.2)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
autotest (~> 4.4.4)
|
36
|
+
autotest-fsevent (~> 0.2.3)
|
37
|
+
autotest-growl (~> 0.2.6)
|
38
|
+
highline (~> 1.6.1)
|
39
|
+
rcov (~> 0.9.9)
|
40
|
+
rspec (~> 2.1.0)
|
41
|
+
runssh!
|
42
|
+
trollop (~> 1.16.2)
|
data/README.rdoc
CHANGED
@@ -53,18 +53,16 @@ of bookmarks:
|
|
53
53
|
|
54
54
|
You can decide to arrange your bookmarks by customers and/or location
|
55
55
|
and/or internal/external addresses etc. To access a host definition you
|
56
|
-
specify the full path to that host. In the above example to access
|
56
|
+
specify the full path to that host. In the above example to access <i>host2</i>
|
57
57
|
(e.g, print it's definition) run:
|
58
58
|
runssh print customer1 location1 host2
|
59
59
|
|
60
60
|
== requirements:
|
61
|
-
(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
* rspec
|
67
|
-
* rcov
|
61
|
+
Dependencies (runtime and development) are specified in <i>runssh.gemspec</i>.
|
62
|
+
When installing through gem, dependencies will be installed by default. For
|
63
|
+
development, you need to install _bundler_ 1.0.x and run (inside the
|
64
|
+
project directory):
|
65
|
+
bundle install
|
68
66
|
|
69
67
|
== License
|
70
68
|
This program is distributed under the GPL v2 license.
|
@@ -76,12 +74,33 @@ This program is distributed under the GPL v2 license.
|
|
76
74
|
[0.1.1] Improved docs.
|
77
75
|
|
78
76
|
== TODO
|
77
|
+
* Convert to use thor as parent for RunSSHLib::CLI
|
79
78
|
* Create a _proper_ zsh completion script.
|
80
79
|
* Add scp capabilities
|
81
80
|
* Add tunneling support:
|
82
81
|
1. Configured tunneling
|
83
82
|
2. tunneling defined on the command line.
|
84
83
|
* Remote commands (e.g, with no login).
|
84
|
+
* Shell via gateway (connect to a firewall and from there open shell to the host).
|
85
85
|
* Rename (or move) host definition
|
86
86
|
* Maybe replace invoking ssh from the command line with some library.
|
87
|
-
* Automatic deletion of empty groups.
|
87
|
+
* Automatic deletion of empty groups.
|
88
|
+
|
89
|
+
=== Tasks for 0.2
|
90
|
+
* Add support for autotest ✓
|
91
|
+
* Use Highline for questions ✓
|
92
|
+
* Migrate to new ConfigFile:
|
93
|
+
1. Create a new _SshHostDef_ class ✓
|
94
|
+
2. Add version to the config file (use "VERSION" key as string and not as
|
95
|
+
symbol (so it won't interfere with the paths) ✓
|
96
|
+
3. Create discovery of config version on initialization:
|
97
|
+
* If config file is un-versioned abort the application and suggest
|
98
|
+
running with <tt>--update-config</tt> argument. ✓
|
99
|
+
4. Create a update-config method for upgrading the configurations file:
|
100
|
+
* Inform the user of backup name. ✓
|
101
|
+
* Copy the original to backup. ✓
|
102
|
+
* Convert all _HostDef_ instances to _SshHostDef_. ✓
|
103
|
+
* Save configuration and inform the user. ✓
|
104
|
+
4. Make sure _VERSION_ is excluded form the listing of groups. ✓
|
105
|
+
5. Convert existing functionality with the new configuration. ✓
|
106
|
+
* Add support for remote command. ✓
|
data/Rakefile
CHANGED
@@ -16,49 +16,21 @@
|
|
16
16
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
17
17
|
#
|
18
18
|
|
19
|
-
require '
|
20
|
-
require 'rubygems'
|
21
|
-
require 'rake'
|
19
|
+
require 'bundler'
|
22
20
|
require 'rake/rdoctask'
|
23
|
-
require 'rake/gempackagetask'
|
24
21
|
require 'rspec/core/rake_task'
|
25
22
|
|
26
|
-
|
27
|
-
s.platform = Gem::Platform::RUBY
|
28
|
-
s.summary = "CLI utility to bookmark multiple ssh connections with hierarchy."
|
29
|
-
s.name = 'runssh'
|
30
|
-
s.version = RunSSHLib::Version::STRING
|
31
|
-
s.homepage = 'http://github.com/babysnakes/runssh'
|
32
|
-
s.required_ruby_version = '~> 1.8.7'
|
33
|
-
s.has_rdoc = true
|
34
|
-
s.extra_rdoc_files = ['README.rdoc']
|
35
|
-
s.rdoc_options << '--main' << 'README.rdoc'
|
36
|
-
s.author = 'Haim Ashkenazi'
|
37
|
-
s.email = 'haim@babysnakes.org'
|
38
|
-
s.add_dependency('trollop', '~> 1.16.2')
|
39
|
-
s.add_development_dependency('rspec', "~> 2.0.1")
|
40
|
-
s.add_development_dependency('rcov', '~> 0.9.9')
|
41
|
-
s.require_path = 'lib'
|
42
|
-
s.executables << 'runssh'
|
43
|
-
s.files = %w(README.rdoc gpl-2.0.txt Rakefile) + Dir.glob("{lib,bin,spec}/**/*")
|
44
|
-
s.description = <<EOF
|
45
|
-
Runssh is a command line utility to help bookmark many
|
46
|
-
ssh connections in heirarchial groups.
|
47
|
-
EOF
|
48
|
-
end
|
49
|
-
|
50
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
51
|
-
pkg.need_zip = true
|
52
|
-
pkg.need_tar = true
|
53
|
-
end
|
23
|
+
Bundler::GemHelper.install_tasks
|
54
24
|
|
55
25
|
RSpec::Core::RakeTask.new do |t|
|
56
|
-
t.rcov = true
|
26
|
+
t.rcov = ENV['rcov'] == "true" ? true : false
|
57
27
|
t.rcov_opts = %w(--exclude gems\/,spec\/)
|
28
|
+
t.name = :specs
|
58
29
|
# t.warning = true # rspec produces too many warnings so it's commented.
|
59
30
|
end
|
60
31
|
|
61
32
|
Rake::RDocTask.new do |rd|
|
62
33
|
rd.main = "README.rdoc"
|
63
34
|
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
35
|
+
rd.options << "-c" << 'UTF-8'
|
64
36
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
data/bin/runssh
CHANGED
@@ -19,10 +19,12 @@
|
|
19
19
|
#
|
20
20
|
|
21
21
|
require 'rubygems'
|
22
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
22
23
|
require 'runsshlib'
|
23
24
|
|
24
25
|
# verify required versions
|
25
26
|
gem 'trollop', "1.16.2"
|
27
|
+
gem 'highline', '1.6.1'
|
26
28
|
|
27
29
|
# let's clone and reset ARGV for future use
|
28
30
|
args = ARGV.clone
|
data/lib/runsshlib/cli.rb
CHANGED
@@ -28,12 +28,12 @@ module RunSSHLib
|
|
28
28
|
args.unshift '-h' if args.empty?
|
29
29
|
args.unshift '-h' if args == ['help']
|
30
30
|
@global_options = parse_args(args)
|
31
|
+
return if @global_options[:update_config]
|
31
32
|
|
32
33
|
# workaround to enable 'help COMMAND' functionality.
|
33
34
|
if args.first == 'help'; args.shift; args << '-h'; end
|
34
35
|
# indicate path completion request
|
35
36
|
@completion_requested = args.delete('?')
|
36
|
-
|
37
37
|
@cmd = extract_subcommand(args)
|
38
38
|
@options = parse_subcommand(@cmd, args)
|
39
39
|
@c = init_config
|
@@ -41,12 +41,23 @@ module RunSSHLib
|
|
41
41
|
@path = args.map { |e| e.to_sym }
|
42
42
|
rescue ConfigError, InvalidSubCommandError, Errno::ENOENT => e
|
43
43
|
Trollop.die e.message
|
44
|
+
rescue OlderConfigVersionError => e
|
45
|
+
message = <<-EOM
|
46
|
+
You seem to use older configuration version. Did you upgrade runssh?
|
47
|
+
If so, please run <%= color('runssh [ -f config ] --update-config', :blue) %> in order to
|
48
|
+
update your configuration to the current version.
|
49
|
+
|
50
|
+
Your old configuration will be saved with the suffix <%= color(".#{e.message}", :underline) %>
|
51
|
+
EOM
|
52
|
+
HighLine.new.say(message)
|
53
|
+
abort ''
|
44
54
|
end
|
45
55
|
|
46
56
|
# run
|
47
57
|
def run
|
48
|
-
|
49
|
-
|
58
|
+
if @global_options[:update_config]
|
59
|
+
run_update_config
|
60
|
+
elsif @completion_requested
|
50
61
|
puts @c.list_groups(@path)
|
51
62
|
else
|
52
63
|
command_name = 'run_' + @cmd
|
@@ -92,6 +103,9 @@ Global options:
|
|
92
103
|
EOS
|
93
104
|
opt :config_file, "alternate config file",
|
94
105
|
:type => :string, :short => :f
|
106
|
+
opt :update_config, "update configuration from previous version." +
|
107
|
+
" this option should run without COMMAND",
|
108
|
+
:short => :U
|
95
109
|
version "RunSSH version #{Version::STRING}"
|
96
110
|
stop_on_unknown
|
97
111
|
end
|
@@ -119,19 +133,32 @@ EOS
|
|
119
133
|
def parse_subcommand(cmd, args)
|
120
134
|
case cmd
|
121
135
|
when 'shell'
|
122
|
-
Trollop::options(args) do
|
136
|
+
options = Trollop::options(args) do
|
123
137
|
banner <<-EOS
|
124
|
-
Usage: runssh [global_options] shell [options] <path>
|
138
|
+
Usage: runssh [global_options] shell [options] <path> [-- <remote command>]
|
125
139
|
|
126
140
|
Connect to the specified host using ssh.
|
127
141
|
|
128
142
|
<path> : See main help for description of path.
|
129
143
|
|
144
|
+
If you only want to run remote command instead of full shell, you can
|
145
|
+
append "-- <remote command>" to the regular command. To list /tmp on a host
|
146
|
+
bookmarked as "some host" run:
|
147
|
+
runssh shell some host -- ls -l /tmp
|
148
|
+
|
130
149
|
Options:
|
131
150
|
EOS
|
132
151
|
opt :login, "override the login in the configuration",
|
133
152
|
:type => :string
|
153
|
+
stop_on "--"
|
134
154
|
end
|
155
|
+
# handle the case of remote command (indicated by --)
|
156
|
+
if ind = args.index("--")
|
157
|
+
rmt = args.slice!(ind, args.size - ind)
|
158
|
+
rmt.delete_at(0) # remove --
|
159
|
+
options[:remote_cmd] = rmt.join(" ")
|
160
|
+
end
|
161
|
+
options
|
135
162
|
when 'add'
|
136
163
|
Trollop::options(args) do
|
137
164
|
banner <<-EOS
|
@@ -147,8 +174,8 @@ Options:
|
|
147
174
|
EOS
|
148
175
|
opt :host_name, 'The name or address of the host (e.g, host.example.com)',
|
149
176
|
:short => :n, :type => :string, :required => true
|
150
|
-
opt :
|
151
|
-
:
|
177
|
+
opt :login, 'The user to connect as (optional)',
|
178
|
+
:type => :string
|
152
179
|
end
|
153
180
|
when 'update'
|
154
181
|
Trollop::options(args) do
|
@@ -165,8 +192,8 @@ Options:
|
|
165
192
|
EOS
|
166
193
|
opt :host_name, 'The name or address of the host (e.g, host.example.com)',
|
167
194
|
:short => :n, :type => :string, :required => true
|
168
|
-
opt :
|
169
|
-
:
|
195
|
+
opt :login, 'The user to connect as (optional)',
|
196
|
+
:type => :string
|
170
197
|
end
|
171
198
|
when 'del'
|
172
199
|
Trollop::options(args) do
|
@@ -224,32 +251,37 @@ EOS
|
|
224
251
|
end
|
225
252
|
|
226
253
|
def init_config
|
227
|
-
config = @global_options[:config_file]
|
228
|
-
@global_options[:config_file] : DEFAULT_CONFIG
|
254
|
+
config = @global_options[:config_file] || DEFAULT_CONFIG
|
229
255
|
ConfigFile.new(config)
|
230
256
|
end
|
231
257
|
|
232
258
|
def run_shell(path)
|
233
259
|
host = @c.get_host(path)
|
234
|
-
|
235
|
-
|
260
|
+
# only override if value exist
|
261
|
+
# TODO: this works only for some types (e.g, not boolean) but
|
262
|
+
# currently this is all we need. We may need to make it better
|
263
|
+
# later.
|
264
|
+
definition = host.definition.merge(@options) do |key, this, other|
|
265
|
+
other ? other : this
|
266
|
+
end
|
267
|
+
SshBackend.shell(definition)
|
236
268
|
end
|
237
269
|
|
238
270
|
def run_add(path)
|
239
271
|
# extract the host definition name
|
240
272
|
host = path.pop
|
241
|
-
@
|
242
|
-
|
273
|
+
options = extract_definition @options
|
274
|
+
@c.add_host_def(path, host, SshHostDef.new(options))
|
243
275
|
end
|
244
276
|
|
245
277
|
def run_update(path)
|
246
|
-
@c.update_host_def(path,
|
247
|
-
HostDef.new(@options[:host_name], @options[:user]))
|
278
|
+
@c.update_host_def(path, SshHostDef.new(extract_definition @options))
|
248
279
|
end
|
249
280
|
|
250
281
|
def run_del(path)
|
251
|
-
question = "Are you sure you want to delete \"" + path.join(':') + "\""
|
252
|
-
|
282
|
+
question = "Are you sure you want to delete \"" + path.join(':') + "\"" +
|
283
|
+
"? [yes/no] "
|
284
|
+
if HighLine.new.agree(question)
|
253
285
|
@c.delete_path(path)
|
254
286
|
else
|
255
287
|
puts 'canceled'
|
@@ -258,17 +290,15 @@ EOS
|
|
258
290
|
|
259
291
|
def run_print(path)
|
260
292
|
host = @c.get_host(path)
|
261
|
-
|
262
|
-
|
263
|
-
" * user: #{host.login ? host.login : 'current user'}"
|
264
|
-
puts output
|
293
|
+
puts "Host definition for: #{path.last}"
|
294
|
+
puts host.to_print
|
265
295
|
end
|
266
296
|
|
267
297
|
# we don't use path here, it's just for easier invocation.
|
268
298
|
def run_import(path)
|
269
299
|
question = "Importing a file OVERWRITES existing configuration. " +
|
270
|
-
"Are you sure"
|
271
|
-
if
|
300
|
+
"Are you sure? [yes/no] "
|
301
|
+
if HighLine.new.agree(question)
|
272
302
|
@c.import(@options[:input_file])
|
273
303
|
else
|
274
304
|
puts 'canceled'
|
@@ -280,13 +310,30 @@ EOS
|
|
280
310
|
@c.export(@options[:output_file])
|
281
311
|
end
|
282
312
|
|
283
|
-
#
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
313
|
+
# extract keys relevant for definition of SshHostDef
|
314
|
+
def extract_definition options
|
315
|
+
valid_definition = [:host_name, :login]
|
316
|
+
options.reject do |key, value|
|
317
|
+
! valid_definition.include?(key)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# updating configurations
|
322
|
+
def run_update_config
|
323
|
+
config = @global_options[:config_file] || DEFAULT_CONFIG
|
324
|
+
c = ConfigFile.new(config, true)
|
325
|
+
result = c.update_config
|
326
|
+
if result
|
327
|
+
message = <<-EOM
|
328
|
+
Your config file is now updated to the approproate version.
|
329
|
+
Your old config file is copied to <%= color("#{result}", :blue) %>.
|
330
|
+
EOM
|
331
|
+
HighLine.new.say(message)
|
332
|
+
elsif
|
333
|
+
message = "Your configuration seems to be at the appropriate version!" +
|
334
|
+
" No update was performed."
|
335
|
+
HighLine.new.say(message)
|
336
|
+
end
|
290
337
|
end
|
291
338
|
end
|
292
339
|
end
|
@@ -27,17 +27,27 @@ module RunSSHLib
|
|
27
27
|
# configuration, but should also be able to import/export
|
28
28
|
# to/from yaml file.
|
29
29
|
class ConfigFile
|
30
|
+
Version = 1.0 # Config version
|
30
31
|
|
31
32
|
# Initialize new ConfigFile. Uses supplied config_file or the default
|
32
33
|
# '~/.runssh'. If file doesn't exist, it issues a warning and creates
|
33
34
|
# a new empty one.
|
34
|
-
def initialize(config_file)
|
35
|
+
def initialize(config_file, old_version=false)
|
35
36
|
@config_file = config_file
|
36
37
|
if File.exists? config_file
|
37
38
|
File.open(config_file) { |io| @config = Marshal.load(io) }
|
39
|
+
if ! @config['VERSION']
|
40
|
+
raise OlderConfigVersionError, 'none' unless old_version
|
41
|
+
elsif @config['VERSION'] > Version
|
42
|
+
# This is for the future, to avoid reading more advanced
|
43
|
+
# configuration version in an old runssh version
|
44
|
+
error = "The configuration file is for a newer version of runssh!"
|
45
|
+
raise ConfigError, error
|
46
|
+
end
|
38
47
|
else
|
39
48
|
# warn "Config file not found. It must be the first time you run this app..."
|
40
49
|
@config = Hash.new
|
50
|
+
@config['VERSION'] = Version
|
41
51
|
save
|
42
52
|
end
|
43
53
|
end
|
@@ -47,14 +57,14 @@ module RunSSHLib
|
|
47
57
|
# path:: An array of symbols that represent the path
|
48
58
|
# for the host. e.g, [:client, :datacenter1].
|
49
59
|
# name:: The name of the host definition as symbol.
|
50
|
-
# host_def:: A
|
60
|
+
# host_def:: A SshHostDef instance.
|
51
61
|
def add_host_def(path, name, host_def)
|
52
62
|
# sanity
|
53
|
-
raise ConfigError.new('Invalid host definition') unless host_def.instance_of?
|
63
|
+
raise ConfigError.new('Invalid host definition') unless host_def.instance_of? SshHostDef
|
54
64
|
|
55
65
|
k = path.inject(@config) do |hsh, key|
|
56
66
|
if hsh.include? key
|
57
|
-
if hsh[key].instance_of?
|
67
|
+
if hsh[key].instance_of? SshHostDef
|
58
68
|
raise ConfigError.new('Cannot override host definition with path!')
|
59
69
|
end
|
60
70
|
hsh[key]
|
@@ -74,7 +84,7 @@ module RunSSHLib
|
|
74
84
|
def update_host_def(path, host_def)
|
75
85
|
# sanity
|
76
86
|
raise ConfigError.new('Invalid host definition!') if not
|
77
|
-
host_def.instance_of?
|
87
|
+
host_def.instance_of? SshHostDef
|
78
88
|
|
79
89
|
# we need to separate the host name from the path
|
80
90
|
# in order to get the key of the host definition.
|
@@ -83,7 +93,7 @@ module RunSSHLib
|
|
83
93
|
raise ConfigError, 'Invalid path!' unless groups
|
84
94
|
if groups.include? host
|
85
95
|
raise ConfigError.new("Cannot overwrite group with host definition") unless
|
86
|
-
groups[host].instance_of?
|
96
|
+
groups[host].instance_of? SshHostDef
|
87
97
|
groups[host] = host_def
|
88
98
|
else
|
89
99
|
raise ConfigError.new("Host definition doesn't exist!")
|
@@ -110,7 +120,7 @@ module RunSSHLib
|
|
110
120
|
def list_groups(path)
|
111
121
|
value = retrieve_path(path, 'Invalid path!')
|
112
122
|
if value.instance_of? Hash
|
113
|
-
value.keys
|
123
|
+
value.keys.reject { |i| i == 'VERSION' }
|
114
124
|
else
|
115
125
|
[]
|
116
126
|
end
|
@@ -124,7 +134,7 @@ module RunSSHLib
|
|
124
134
|
value = retrieve_path(path, 'Invalid path!')
|
125
135
|
raise ConfigError.new('Invalid path!') unless value
|
126
136
|
|
127
|
-
if value[mykey].instance_of?
|
137
|
+
if value[mykey].instance_of? SshHostDef or value[mykey] == {}
|
128
138
|
value.delete(mykey)
|
129
139
|
elsif not value[mykey]
|
130
140
|
raise ConfigError.new('Invalid path!')
|
@@ -135,25 +145,46 @@ module RunSSHLib
|
|
135
145
|
save
|
136
146
|
end
|
137
147
|
|
138
|
-
#
|
148
|
+
# Import config from YAML from the specified file.
|
139
149
|
def import(file)
|
140
150
|
require 'yaml'
|
141
|
-
|
151
|
+
config = YAML.load_file(file)
|
152
|
+
raise ConfigError, "The imported file is from a different version of " +
|
153
|
+
"runssh (config: #{config['VERSION']})! aborting." unless
|
154
|
+
config['VERSION'] == Version
|
155
|
+
@config = config
|
142
156
|
save
|
143
157
|
end
|
144
158
|
|
145
|
-
#
|
159
|
+
# Export config as YAML to the supplied file.
|
146
160
|
def export(file)
|
147
161
|
require 'yaml'
|
148
162
|
File.open(file, 'w') { |out| YAML.dump(@config, out) }
|
149
163
|
end
|
150
164
|
|
165
|
+
# Spacial case - perform update to the configuration. This should
|
166
|
+
# later include handling of +all+ versions of the config!
|
167
|
+
#
|
168
|
+
# Returns the name of the backup file or nil if there was no need
|
169
|
+
# for backup.
|
170
|
+
def update_config
|
171
|
+
return if @config['VERSION'] == Version
|
172
|
+
backup_file = @config_file + '.none'
|
173
|
+
require 'fileutils'
|
174
|
+
new_config = config_none_to_10(@config)
|
175
|
+
FileUtils.move(@config_file, backup_file)
|
176
|
+
@config = new_config
|
177
|
+
@config['VERSION'] = Version
|
178
|
+
save
|
179
|
+
backup_file
|
180
|
+
end
|
181
|
+
|
151
182
|
private
|
152
183
|
|
153
184
|
def save
|
154
|
-
require '
|
185
|
+
require 'fileutils'
|
155
186
|
# create backup (File.copy always seems to overwrite existing file)
|
156
|
-
|
187
|
+
FileUtils.copy(@config_file, @config_file + '.bak') if File.exists? @config_file
|
157
188
|
File.open(@config_file, 'w') { |out| Marshal.dump(@config, out) }
|
158
189
|
end
|
159
190
|
|
@@ -163,5 +194,22 @@ module RunSSHLib
|
|
163
194
|
hsh[ky]
|
164
195
|
end
|
165
196
|
end
|
197
|
+
|
198
|
+
# convert the config hash from version none (runssh 0.1) to version 1.0
|
199
|
+
# group is the hash that holds all the groups/hostdefs (@config).
|
200
|
+
def config_none_to_10 group
|
201
|
+
group.each do |key, value|
|
202
|
+
case
|
203
|
+
when value.instance_of?(RunSSHLib::HostDef)
|
204
|
+
hsh = Hash.new
|
205
|
+
hsh[:host_name] = value.name
|
206
|
+
hsh[:login] = value.login if value.login
|
207
|
+
group[key] = RunSSHLib::SshHostDef.new(hsh)
|
208
|
+
when value.instance_of?(Hash)
|
209
|
+
config_none_to_10(value)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
group
|
213
|
+
end
|
166
214
|
end
|
167
215
|
end
|
@@ -18,19 +18,24 @@
|
|
18
18
|
|
19
19
|
module RunSSHLib
|
20
20
|
|
21
|
-
# A
|
22
|
-
|
23
|
-
|
24
|
-
def initialize(host_def, overrides)
|
25
|
-
@host = host_def.name
|
26
|
-
@user = overrides[:login] ? overrides[:login] : host_def.login
|
27
|
-
end
|
21
|
+
# A collection of ssh procedures.
|
22
|
+
module SshBackend
|
23
|
+
module_function
|
28
24
|
|
29
25
|
# run shell on remote host.
|
30
|
-
|
31
|
-
|
26
|
+
# definition:: A Hash containing required data for
|
27
|
+
# making shell connection (e.g., :host_name, :login).
|
28
|
+
#
|
29
|
+
# For running remote commands add to the _definition_ hash
|
30
|
+
# the entire remote command as a string with a +:remote_cmd+ key.
|
31
|
+
def shell(definition)
|
32
|
+
raise "no hostname" unless definition[:host_name] # should never happen
|
33
|
+
command = "ssh "
|
34
|
+
command << "-l #{definition[:login]} " if definition[:login]
|
35
|
+
command << "#{definition[:host_name]}"
|
36
|
+
command << %( -- "#{definition[:remote_cmd]}") if
|
37
|
+
(definition[:remote_cmd] && (!definition[:remote_cmd].empty?))
|
32
38
|
exec command
|
33
39
|
end
|
34
|
-
|
35
40
|
end
|
36
41
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2010 Haim Ashkenazi
|
3
|
+
#
|
4
|
+
# This program is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU General Public License
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
7
|
+
# of the License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
17
|
+
#
|
18
|
+
module RunSSHLib
|
19
|
+
class SshHostDef
|
20
|
+
attr_reader :definition
|
21
|
+
|
22
|
+
# Initialize ssh host def with definition.
|
23
|
+
#
|
24
|
+
# [definition] A hash containing ssh options. <i>:host_name</i> is required.
|
25
|
+
# Could also be a hostname (string) for quick defining SshHostDef
|
26
|
+
# with only hostname.
|
27
|
+
def initialize(definition)
|
28
|
+
if definition.instance_of? String
|
29
|
+
definition = { :host_name => definition }
|
30
|
+
end
|
31
|
+
raise ArgumentError, "Missing hostname" unless definition[:host_name]
|
32
|
+
@definition = definition
|
33
|
+
end
|
34
|
+
|
35
|
+
# should be equal if @definition is equal
|
36
|
+
def ==(other)
|
37
|
+
return false if other.nil?
|
38
|
+
return false unless other.instance_of? SshHostDef
|
39
|
+
definition == other.definition
|
40
|
+
end
|
41
|
+
alias_method :eql?, :==
|
42
|
+
|
43
|
+
def to_print
|
44
|
+
out = " * host: #{definition[:host_name]}"
|
45
|
+
out << "\n * login: #{definition[:login] || 'current user'}"
|
46
|
+
out
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|