runssh 0.1.1 → 0.2.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/.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
|