runssh 0.2.2 → 0.4.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.
@@ -0,0 +1,77 @@
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
+
19
+ Then /^I should get a "([^"]*)" error$/ do |error|
20
+ expect {
21
+ capture(:stderr, @input) do
22
+ cli = RunSSHLib::CLI.new(@args)
23
+ cli.run
24
+ end
25
+ }.to exit_abnormaly
26
+ @buf.should include(error)
27
+ end
28
+
29
+ Then /^I should be prompted with "([^"]*)"$/ do |output|
30
+ # I'm only interested in the output verification but I should hide the errors.
31
+ capture(:stderr) do
32
+ capture(:stdout, 'n\n') do
33
+ expect { RunSSHLib::CLI.new(@args).run }.to exit_abnormaly
34
+ end
35
+ end
36
+ @buf.should match(/#{output}/)
37
+ end
38
+
39
+ When /^I confirm the prompt$/ do
40
+ When %Q(I answer "yes" at the prompt)
41
+ end
42
+
43
+ When /^I answer "([^"]*)" at the prompt$/ do |input|
44
+ @input = input
45
+ end
46
+
47
+ Then /^It should run successfully$/ do
48
+ capture(:stdout, @input) do
49
+ RunSSHLib::CLI.new(@args).run
50
+ end
51
+ end
52
+
53
+ Then /^It should exit normally$/ do
54
+ expect {
55
+ capture(:stdout, @input) do
56
+ cli = RunSSHLib::CLI.new(@args).run
57
+ end
58
+ }.to exit_normaly
59
+ end
60
+
61
+ Then /^The output should include "([^"]*)"$/ do |output|
62
+ @buf.should match(/#{output}/)
63
+ end
64
+
65
+ Then /^The output should not include "([^"]*)"$/ do |output|
66
+ @buf.should_not match(/#{output}/)
67
+ end
68
+
69
+ Then /^It should execute "(.*)"$/ do |command|
70
+ capture(:stdout) {
71
+ RunSSHLib::CLI.new(@args).run
72
+ }.should match(/^#{command}\s*\n$/)
73
+ end
74
+
75
+ Then /^The output file should contain "([^"]*)"$/ do |output|
76
+ File.read(TMP_YML).should include(output)
77
+ end
@@ -0,0 +1,36 @@
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
+
19
+ require "#{File.expand_path('../../../spec/support/utils', __FILE__)}"
20
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
21
+
22
+ require 'simplecov'
23
+ SimpleCov.start do
24
+ add_group "Sources", "/lib/"
25
+ end
26
+ require 'cucumber/rspec/doubles'
27
+
28
+ Before do |scenario|
29
+ stub_ssh_exec
30
+ @test_args = %W(-f #{TMP_FILE})
31
+ @input = ''
32
+ end
33
+
34
+ After do |scenario|
35
+ cleanup_tmp_file
36
+ end
@@ -0,0 +1,24 @@
1
+ Feature: Updating bookmarks
2
+ In order to modify existing bookmarks
3
+ I want to run runssh update on existing bookmark. This command should fail
4
+ if the bookmark doesn't exist yet (to avoid errors).
5
+ Currently the update command COMPLETELY overwrites the existing bookmark so
6
+ it's not very useful (to be fixed later).
7
+
8
+ Scenario: All available options runs successfully
9
+ Given Bookmark "one two three" exist with:
10
+ | name | value |
11
+ | host-name | some.host |
12
+ | login | somelogin |
13
+ When I run the "update" command with "one two three -n some.other.host -l otherlogin -L 8080:localhost:8080"
14
+ Then It should run successfully
15
+ And Bookmark "one two three" should contain:
16
+ | name | value |
17
+ | host_name | some.other.host |
18
+ | login | otherlogin |
19
+ | local_tunnel | 8080:localhost:8080 |
20
+
21
+ Scenario: Fails when updating non-existing bookmark
22
+ Given Empty database
23
+ When I run the "update" command with "one two -n somehost"
24
+ Then I should get a "Error: Invalid path!" error
data/lib/runsshlib/cli.rb CHANGED
@@ -20,14 +20,42 @@ require 'trollop'
20
20
  module RunSSHLib
21
21
  class CLI
22
22
 
23
- COMMAND = %w(shell add del update print import export)
23
+ COMMAND = %w(shell add del update print import export cpid)
24
+ MAIN_HELP = <<-EOS
25
+ Usage: runssh [global_options] COMMAND [options] <path>
26
+
27
+ A utility to bookmark multiple ssh connections in heirarchial order.
28
+ For a better understanding of host definitions and bookmarks, Read
29
+ the provided README.rdoc or go to http://github.com/babysnakes/runssh.
30
+
31
+ COMMAND : One of the commands mentioned below. It's possible to
32
+ type only part of the command as long as it's not ambiguous.
33
+ <path> : A space-separated list of names (e.g, one two three) that
34
+ leads to a host definition. For available completions
35
+ append " ?" to the end of path.
36
+
37
+ Available commands:
38
+ * shell : Open ssh shell on remote host
39
+ * add : Add host definition
40
+ * del : Delete host definition
41
+ * update : Update host definition
42
+ * print : Print host definition
43
+ * import : Import configuration
44
+ * export : Export configuration
45
+ * cpid : Copy ssh public key to authorized_keys on remote host
46
+
47
+ For help on commands run:
48
+ runssh help COMMAND
49
+
50
+ Global options:
51
+ EOS
24
52
 
25
53
  # Initialize new CLI instance and parse the supplied
26
54
  # arguments.
27
55
  def initialize(args)
28
56
  args.unshift '-h' if args.empty?
29
- args.unshift '-h' if args == ['help']
30
57
  @global_options = parse_args(args)
58
+ exit_with_help if args == ['help']
31
59
  return if @global_options[:update_config]
32
60
 
33
61
  # workaround to enable 'help COMMAND' functionality.
@@ -42,15 +70,9 @@ module RunSSHLib
42
70
  rescue ConfigError, InvalidSubCommandError, Errno::ENOENT => e
43
71
  Trollop.die e.message
44
72
  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
73
+ message = construct_update_config_message e.message
52
74
  HighLine.new.say(message)
53
- abort ''
75
+ exit 1
54
76
  end
55
77
 
56
78
  # run
@@ -66,6 +88,8 @@ EOM
66
88
  end
67
89
  rescue ConfigError => e
68
90
  Trollop.die e.message
91
+ rescue AbortError => e
92
+ abort e.message
69
93
  end
70
94
 
71
95
  private
@@ -74,33 +98,7 @@ EOM
74
98
  def parse_args(args)
75
99
  Trollop::options(args) do
76
100
  # TODO: This should be generated automatically somehow!!
77
- banner <<-EOS
78
- Usage: runssh [global_options] COMMAND [options] <path>
79
-
80
- A utility to bookmark multiple ssh connections in heirarchial order.
81
- For a better understanding of host definitions and bookmarks, Read
82
- the provided README.rdoc or go to http://github.com/babysnakes/runssh.
83
-
84
- COMMAND : One of the commands mentioned below. It's possible to
85
- type only part of the command as long as it's not ambiguous.
86
- <path> : A space-separated list of names (e.g, one two three) that
87
- leads to a host definition. For available completions
88
- append " ?" to the end of path.
89
-
90
- Available commands:
91
- * shell : Open ssh shell on remote host
92
- * add : Add host definition
93
- * del : Delete host definition
94
- * update : Update host definition
95
- * print : Print host definition
96
- * import : Import configuration
97
- * export : Export configuration
98
-
99
- For help on commands run:
100
- runssh help COMMAND
101
-
102
- Global options:
103
- EOS
101
+ banner MAIN_HELP
104
102
  opt :config_file, "alternate config file",
105
103
  :type => :string, :short => :f
106
104
  opt :update_config, "update configuration from previous version." +
@@ -111,8 +109,8 @@ EOS
111
109
  end
112
110
  end
113
111
 
114
- # Etracts the subcommand from args. Throws InvalidSubCommandError if
115
- # invalid or ambigious subcommand
112
+ # Extracts the subcommand from args. Throws InvalidSubCommandError if
113
+ # invalid or ambiguous subcommand
116
114
  def extract_subcommand(args)
117
115
  cmd = args.shift
118
116
  if COMMAND.include? cmd
@@ -127,14 +125,34 @@ EOS
127
125
  raise InvalidSubCommandError, 'invalid command'
128
126
  end
129
127
 
130
- # handles argument parsing for all subcomand. It doesn't contain
131
- # any logic, nor does it handle errors. It just parses the
132
- # arguments and put the result into @options.
128
+ # route argument parsing for all subcommand.
133
129
  def parse_subcommand(cmd, args)
134
130
  case cmd
135
131
  when 'shell'
136
- options = Trollop::options(args) do
137
- banner <<-EOS
132
+ parse_shell args
133
+ when 'add', 'update'
134
+ parse_add_update cmd, args
135
+ when 'del'
136
+ parse_del args
137
+ when 'print'
138
+ parse_print args
139
+ when 'import'
140
+ parse_import args
141
+ when 'export'
142
+ parse_export args
143
+ when 'cpid'
144
+ parse_cpid args
145
+ end
146
+ end
147
+
148
+ def init_config
149
+ config = @global_options[:config_file] || DEFAULT_CONFIG
150
+ ConfigFile.new(config)
151
+ end
152
+
153
+ def parse_shell(args)
154
+ options = Trollop::options(args) do
155
+ banner <<-EOS
138
156
  Usage: runssh [global_options] shell [options] <path> [-- <remote command>]
139
157
 
140
158
  Connect to the specified host using ssh.
@@ -145,68 +163,128 @@ If you only want to run remote command instead of full shell, you can
145
163
  append "-- <remote command>" to the regular command. To list /tmp on a host
146
164
  bookmarked as "some host" run:
147
165
  runssh shell some host -- ls -l /tmp
166
+ Remote command enables pseudo terminal (ssh -t) by default. To disable
167
+ use -T.
148
168
 
149
169
  (Local) tunneling can be enabled with the -L options (correspond to
150
170
  ssh -L option). An abbreviated syntax could be used as the requested
151
171
  port if both ports are identical and host is localhost.
152
172
  e.g. -L 7070 is converted to -L 7070:localhost:7070
153
173
 
174
+ In case of conflicting host key (e.g, when reinstalling a server), ssh refuses
175
+ to connect and tells you which line has the conflicting host key. ONLY if you
176
+ know for sure why you have a conflicting key, you can add the
177
+ --insecure-host-key option with the conflicting line as an argument. DON'T
178
+ DO THAT UNLESS YOU KNOW WHY THE KEY HAS CHANGED!
179
+
154
180
  Options:
155
181
  EOS
156
- opt :login, "override the login in the configuration",
157
- :type => :string
158
- opt :host_name, 'override the name or address of the host',
159
- :short => :n, :type => :string
160
- opt :local_tunnel, "tunnel definition",
161
- :short => :L, :type => :string
162
- stop_on "--"
163
- end
164
- # handle the case of remote command (indicated by --)
165
- if ind = args.index("--")
166
- rmt = args.slice!(ind, args.size - ind)
167
- rmt.delete_at(0) # remove --
168
- options[:remote_cmd] = rmt.join(" ")
169
- end
170
- options
171
- when 'add'
172
- Trollop::options(args) do
173
- banner <<-EOS
182
+ opt :login, "Override the login in the configuration.",
183
+ :type => :string
184
+ opt :host_name, 'Override the name or address of the host.',
185
+ :short => :n, :type => :string
186
+ opt :local_tunnel, "Tunnel definition (see description above).",
187
+ :short => :L, :type => :string
188
+ opt :no_pseudo_terminal, 'Disable pseudo terminal ' \
189
+ '(effective only with remote command).', :short => :T
190
+ opt :insecure_host_key, 'delete the specified line form known hosts ' \
191
+ 'file. EXPERIMENTAL and DANGEROUS!.', :type => :int, :short => :I
192
+ opt :option, 'Ssh option. Appended to saved ssh options. ' \
193
+ 'Can be used multiple times.',
194
+ :short => :o, :type => :string, :multi => true
195
+ stop_on "--"
196
+ end
197
+ # handle the case of remote command (indicated by --)
198
+ if ind = args.index("--")
199
+ rmt = args.slice!(ind, args.size - ind)
200
+ rmt.delete_at(0) # remove --
201
+ options[:remote_cmd] = rmt.join(" ")
202
+ end
203
+ options
204
+ end
205
+
206
+ def parse_cpid(args)
207
+ Trollop::options(args) do
208
+ banner <<-EOH
209
+ Usage: runssh [global_options] cpid [options] <path>
210
+
211
+ Copy ssh public key to authorized_keys on remote host. If no id file is
212
+ specified, It copies all the keys in your ssh-agent.
213
+
214
+ Requires the `ssh-copy-id` command to be in your path.
215
+ See manpage for ssh-copy-id for more details.
216
+
217
+ <path> : See main help for description of path.
218
+
219
+ Options:
220
+ EOH
221
+ opt :identity_file, "Full path to identity file.",
222
+ :short => :i, :type => :string
223
+ end
224
+ end
225
+
226
+ def parse_add_update(cmd, args)
227
+ case cmd
228
+ when "add"
229
+ help = <<-EOH
174
230
  Usage: runssh [global_options] add [options] <path>
175
231
 
176
232
  Add a new host definition at the supplied <path>. <path> must not exist!
177
- A host definition can have a hostname (required) and a remote user
178
- (optional).
233
+ A host definition must have a hostname. All other options (see below)
234
+ are optional.
179
235
 
180
236
  <path> : See main help for description of path.
181
237
 
238
+ (Local) tunneling can be added with the -L options (correspond to
239
+ ssh -L option). An abbreviated syntax could be used as the requested
240
+ port if both ports are identical and host is localhost.
241
+ e.g. -L 7070 is converted to -L 7070:localhost:7070
242
+
182
243
  Options:
183
- EOS
184
- opt :host_name, 'The name or address of the host (e.g, host.example.com)',
185
- :short => :n, :type => :string, :required => true
186
- opt :login, 'The user to connect as (optional)',
187
- :type => :string
188
- end
189
- when 'update'
190
- Trollop::options(args) do
191
- banner <<-EOS
244
+ EOH
245
+ when "update"
246
+ help = <<-EOH
192
247
  Usage: runssh [global_options] update [options] <path>
193
248
 
194
249
  Update host definition specified by <path> with new settings. The host
195
250
  definition is completely replaced by the new definition (e.g, You can
196
- not specify only new host and expect the user to remain the old one).
251
+ not specify only new host and expect the login to remain the existing one).
197
252
 
198
253
  <path> : See main help for description of path.
199
254
 
255
+ (Local) tunneling can be added with the -L options (correspond to
256
+ ssh -L option). An abbreviated syntax could be used as the requested
257
+ port if both ports are identical and host is localhost.
258
+ e.g. -L 7070 is converted to -L 7070:localhost:7070
259
+
200
260
  Options:
201
- EOS
202
- opt :host_name, 'The name or address of the host (e.g, host.example.com)',
203
- :short => :n, :type => :string, :required => true
204
- opt :login, 'The user to connect as (optional)',
205
- :type => :string
206
- end
207
- when 'del'
208
- Trollop::options(args) do
209
- banner <<-EOS
261
+ EOH
262
+ end
263
+ options = Trollop::options(args) do
264
+ banner help
265
+ opt :host_name, 'The name or address of the host (e.g, host.example.com).',
266
+ :short => :n, :type => :string, :required => true
267
+ opt :login, 'The login to connect as.',
268
+ :type => :string
269
+ opt :local_tunnel, "Tunnel definition (see description above).",
270
+ :short => :L, :type => :string
271
+ opt :option, 'Ssh option (corresponds to ssh -o <option>). ' \
272
+ 'Can be used multiple times.',
273
+ :short => :o, :multi => true, :type => :string
274
+ opt :no_host_key_checking, "DANGEROUS! Don't verify host key when " \
275
+ "connecting to this host. Shortcut for '-o UserKnownHostsFile=" \
276
+ "/dev/null -o StrictHostKeyChecking=no'",
277
+ :short => :N, :type => :boolean
278
+ end
279
+ if options[:no_host_key_checking_given]
280
+ options[:option] << 'UserKnownHostsFile=/dev/null' << 'StrictHostKeyChecking=no'
281
+ end
282
+ options
283
+ end
284
+
285
+ def parse_del(args)
286
+ Trollop::options(args) do
287
+ banner <<-EOS
210
288
  Usage: runssh [global_options] del [options] <path>
211
289
 
212
290
  Delete host definitions or `empty` groups (e.g, groups that contained
@@ -217,11 +295,13 @@ verification.
217
295
 
218
296
  Options:
219
297
  EOS
220
- opt :yes, 'Delete without verification'
221
- end
222
- when 'print'
223
- Trollop::options(args) do
224
- banner <<-EOS
298
+ opt :yes, 'Delete without verification.'
299
+ end
300
+ end
301
+
302
+ def parse_print(args)
303
+ Trollop::options(args) do
304
+ banner <<-EOS
225
305
  Usage: runssh [global_options] print [options] <path>
226
306
 
227
307
  Print host configuration to the console.
@@ -230,52 +310,63 @@ Print host configuration to the console.
230
310
 
231
311
  Options:
232
312
  EOS
233
- end
234
- when 'import'
235
- Trollop::options(args) do
236
- banner <<-EOS
313
+ end
314
+ end
315
+
316
+ def parse_import(args)
317
+ Trollop::options(args) do
318
+ banner <<-EOS
237
319
  Usage: runssh [global_options] import [options]
238
320
 
239
- Imports a new configuration.
321
+ Imports a configuration (The configuration must be in YAML format).
240
322
  CAREFULL: This completely overrides the current configuration!
241
323
 
242
324
  Options:
243
325
  EOS
244
- opt :input_file, 'The yaml file to import from',
245
- :type => :string, :required => true
246
- end
247
- when 'export'
248
- Trollop::options(args) do
249
- banner <<-EOS
326
+ opt :input_file, 'The yaml file to import from.',
327
+ :type => :string, :required => true
328
+ end
329
+ end
330
+
331
+ def parse_export(args)
332
+ Trollop::options(args) do
333
+ banner <<-EOS
250
334
  Usage runssh [global_options] export [options]
251
335
 
252
336
  Exports the configuration to a YAML file.
253
337
 
254
338
  Options
255
339
  EOS
256
- opt :output_file, 'The output file',
257
- :type => :string, :required => true
258
- end
340
+ opt :output_file, 'The output file.',
341
+ :type => :string, :required => true
259
342
  end
260
343
  end
261
344
 
262
- def init_config
263
- config = @global_options[:config_file] || DEFAULT_CONFIG
264
- ConfigFile.new(config)
265
- end
266
-
267
345
  def run_shell(path)
346
+ verify_and_delete_conflicting_host_key(@options[:insecure_host_key]) if
347
+ @options[:insecure_host_key_given]
348
+
268
349
  host = @c.get_host(path)
269
350
  # only override if value exist
270
351
  # TODO: this works only for some types (e.g, not boolean) but
271
352
  # currently this is all we need. We may need to make it better
272
353
  # later.
273
354
  definition = host.definition.merge(@options) do |key, this, other|
274
- other ? other : this
355
+ case key
356
+ when :option
357
+ this + other
358
+ else
359
+ other ? other : this
360
+ end
275
361
  end
276
362
  SshBackend.shell(definition)
277
363
  end
278
364
 
365
+ def run_cpid(path)
366
+ host = @c.get_host(path)
367
+ SshBackend.copy_id(host.definition.merge(@options))
368
+ end
369
+
279
370
  def run_add(path)
280
371
  # extract the host definition name
281
372
  host = path.pop
@@ -288,13 +379,12 @@ EOS
288
379
  end
289
380
 
290
381
  def run_del(path)
291
- question = "Are you sure you want to delete \"" + path.join(':') + "\"" +
292
- "? [yes/no] "
293
- if HighLine.new.agree(question)
294
- @c.delete_path(path)
295
- else
296
- puts 'canceled'
382
+ unless @options[:yes]
383
+ question = %Q(Are you sure you want to delete "#{path.join(':')}") +
384
+ "? [yes/no] "
385
+ @options[:yes] = agree_or_abort(question, "Cancelled")
297
386
  end
387
+ @c.delete_path(path)
298
388
  end
299
389
 
300
390
  def run_print(path)
@@ -307,11 +397,8 @@ EOS
307
397
  def run_import(path)
308
398
  question = "Importing a file OVERWRITES existing configuration. " +
309
399
  "Are you sure? [yes/no] "
310
- if HighLine.new.agree(question)
311
- @c.import(@options[:input_file])
312
- else
313
- puts 'canceled'
314
- end
400
+ agree_or_abort(question, 'Cancelled')
401
+ @c.import(@options[:input_file])
315
402
  end
316
403
 
317
404
  # we don't use path here, it's just for easier invocation
@@ -321,7 +408,7 @@ EOS
321
408
 
322
409
  # extract keys relevant for definition of SshHostDef
323
410
  def extract_definition options
324
- valid_definition = [:host_name, :login]
411
+ valid_definition = [:host_name, :login, :local_tunnel, :option]
325
412
  options.reject do |key, value|
326
413
  ! valid_definition.include?(key)
327
414
  end
@@ -344,5 +431,46 @@ EOM
344
431
  HighLine.new.say(message)
345
432
  end
346
433
  end
434
+
435
+ # help needed out of trollop::parse loop
436
+ def exit_with_help
437
+ p = Trollop::Parser.new do
438
+ banner MAIN_HELP
439
+ end
440
+ Trollop::with_standard_exception_handling p do
441
+ raise Trollop::HelpNeeded
442
+ end
443
+ end
444
+
445
+ def verify_and_delete_conflicting_host_key(line_number)
446
+ khu = RunSSHLib::SshBackend::KnownHostsUtils.new
447
+ host = IO.readlines(khu.known_hosts_file)[line_number - 1].split[0]
448
+ question = "Are you sure you want to delete the key for host: " \
449
+ "'<%= color(\"#{host}\", :red) %>'? " \
450
+ "Conflicting key could indicate compromised host! [yes/no] "
451
+ agree_or_abort(question, "Cancelled")
452
+ khu.delete_line_from_known_hosts_file line_number
453
+ end
454
+
455
+ # Prompts you with the supplied question and aborts with the supplied
456
+ # error unless confirmed the prompt.
457
+ def agree_or_abort(question, error_message)
458
+ raise(AbortError, error_message) unless HighLine.new.agree(question)
459
+ end
460
+
461
+ # Construct update_config message with correct config file.
462
+ # The current_version is the number of the existing config version.
463
+ def construct_update_config_message(current_version)
464
+ config_string = @global_options[:config_file] ?
465
+ "-f #{@global_options[:config_file]}" : ''
466
+ message = <<-EOM
467
+ You seem to use older configuration version. Did you upgrade runssh?
468
+ If so, please run <%= color("runssh #{config_string} --update-config", :blue) %>
469
+ in order to update your configuration to the current version.
470
+
471
+ Your old configuration will be saved with the suffix \
472
+ <%= color(".#{current_version}", :underline) %>
473
+ EOM
474
+ end
347
475
  end
348
476
  end