rouster 0.7 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -3
  3. data/README.md +7 -241
  4. data/Rakefile +18 -55
  5. data/Vagrantfile +8 -26
  6. data/lib/rouster.rb +183 -404
  7. data/lib/rouster/deltas.rb +118 -577
  8. data/lib/rouster/puppet.rb +34 -209
  9. data/lib/rouster/testing.rb +59 -366
  10. data/lib/rouster/tests.rb +19 -70
  11. data/path_helper.rb +7 -5
  12. data/rouster.gemspec +1 -3
  13. data/test/basic.rb +1 -4
  14. data/test/functional/deltas/test_get_groups.rb +2 -74
  15. data/test/functional/deltas/test_get_packages.rb +4 -86
  16. data/test/functional/deltas/test_get_ports.rb +1 -26
  17. data/test/functional/deltas/test_get_services.rb +4 -43
  18. data/test/functional/deltas/test_get_users.rb +2 -35
  19. data/test/functional/puppet/test_facter.rb +1 -41
  20. data/test/functional/puppet/test_get_puppet_star.rb +68 -0
  21. data/test/functional/test_caching.rb +1 -5
  22. data/test/functional/test_dirs.rb +0 -25
  23. data/test/functional/test_get.rb +6 -10
  24. data/test/functional/test_inspect.rb +1 -1
  25. data/test/functional/test_is_file.rb +1 -17
  26. data/test/functional/test_new.rb +22 -233
  27. data/test/functional/test_put.rb +11 -9
  28. data/test/functional/test_restart.rb +4 -1
  29. data/test/functional/test_run.rb +3 -2
  30. data/test/puppet/test_apply.rb +11 -13
  31. data/test/puppet/test_roles.rb +173 -0
  32. data/test/unit/test_new.rb +0 -88
  33. data/test/unit/test_parse_ls_string.rb +0 -67
  34. data/test/unit/testing/test_validate_file.rb +47 -39
  35. data/test/unit/testing/test_validate_package.rb +10 -36
  36. metadata +6 -46
  37. data/.reek +0 -63
  38. data/.travis.yml +0 -11
  39. data/Gemfile +0 -17
  40. data/Gemfile.lock +0 -102
  41. data/LICENSE +0 -9
  42. data/examples/aws.rb +0 -85
  43. data/examples/openstack.rb +0 -61
  44. data/examples/passthrough.rb +0 -71
  45. data/lib/rouster/vagrant.rb +0 -311
  46. data/plugins/aws.rb +0 -347
  47. data/plugins/openstack.rb +0 -136
  48. data/test/functional/deltas/test_get_crontab.rb +0 -161
  49. data/test/functional/deltas/test_get_os.rb +0 -68
  50. data/test/functional/test_is_in_file.rb +0 -40
  51. data/test/functional/test_passthroughs.rb +0 -94
  52. data/test/functional/test_validate_file.rb +0 -131
  53. data/test/unit/puppet/resources/puppet_run_with_failed_exec +0 -59
  54. data/test/unit/puppet/resources/puppet_run_with_successful_exec +0 -61
  55. data/test/unit/puppet/test_get_puppet_star.rb +0 -91
  56. data/test/unit/puppet/test_puppet_parsing.rb +0 -44
  57. data/test/unit/testing/resources/osx-launchd +0 -285
  58. data/test/unit/testing/resources/rhel-systemd +0 -46
  59. data/test/unit/testing/resources/rhel-systemv +0 -41
  60. data/test/unit/testing/resources/rhel-upstart +0 -20
  61. data/test/unit/testing/test_get_services.rb +0 -178
  62. data/test/unit/testing/test_validate_cron.rb +0 -78
  63. data/test/unit/testing/test_validate_port.rb +0 -103
data/lib/rouster.rb CHANGED
@@ -7,241 +7,87 @@ require 'net/ssh'
7
7
  require sprintf('%s/../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
8
8
 
9
9
  require 'rouster/tests'
10
- require 'rouster/vagrant'
11
10
 
12
11
  class Rouster
13
12
 
14
13
  # sporadically updated version number
15
- VERSION = 0.70
14
+ VERSION = 0.41
16
15
 
17
16
  # custom exceptions -- what else do we want them to include/do?
18
- class ArgumentError < StandardError; end # thrown by methods that take parameters from users
19
17
  class FileTransferError < StandardError; end # thrown by get() and put()
20
18
  class InternalError < StandardError; end # thrown by most (if not all) Rouster methods
21
19
  class ExternalError < StandardError; end # thrown when external dependencies do not respond as expected
22
20
  class LocalExecutionError < StandardError; end # thrown by _run()
23
21
  class RemoteExecutionError < StandardError; end # thrown by run()
24
- class PassthroughError < StandardError; end # thrown by anything Passthrough related (mostly vagrant.rb)
25
22
  class SSHConnectionError < StandardError; end # thrown by available_via_ssh() -- and potentially _run()
26
23
 
27
- attr_accessor :facts, :last_puppet_run
28
- attr_reader :cache, :cache_timeout, :deltas, :exitcode, :logger, :name, :output, :passthrough, :retries, :sshkey, :unittest, :vagrantbinary, :vagrantfile
24
+ attr_accessor :facts, :sudo, :verbosity
25
+ attr_reader :cache, :cache_timeout, :deltas, :exitcode, :log, :name, :output, :passthrough, :sshkey, :vagrantfile
29
26
 
30
27
  ##
31
28
  # initialize - object instantiation
32
29
  #
33
30
  # parameters
34
- # * <name> - the name of the VM as specified in the Vagrantfile
35
- # * [cache_timeout] - integer specifying how long Rouster should cache status() and is_available_via_ssh?() results, default is false
36
- # * [logfile] - allows logging to an external file, if passed true, generates a dynamic filename, otherwise uses what is passed, default is false
37
- # * [passthrough] - boolean of whether this is a VM or passthrough, default is false -- passthrough is not completely implemented
38
- # * [retries] - integer specifying number of retries Rouster should attempt when running external (currently only vagrant()) commands
39
- # * [sshkey] - the full or relative path to a SSH key used to auth to VM -- defaults to location Vagrant installs to (ENV[VAGRANT_HOME} or ]~/.vagrant.d/)
40
- # * [sshtunnel] - boolean of whether or not to instantiate the SSH tunnel upon upping the VM, default is true
41
- # * [sudo] - boolean of whether or not to prefix commands run in VM with 'sudo', default is true
42
- # * [vagrantfile] - the full or relative path to the Vagrantfile to use, if not specified, will look for one in 5 directories above current location
43
- # * [vagrant_concurrency] - boolean controlling whether Rouster will attempt to run `vagrant *` if another vagrant process is already running, default is false
44
- # * [vagrant_reboot] - particularly sticky systems restart better if Vagrant does it for us, default is false
45
- # * [verbosity] - an integer representing console level logging, or an array of integers representing console,file level logging - DEBUG (0) < INFO (1) < WARN (2) < ERROR (3) < FATAL (4)
31
+ # * <name> - the name of the VM as specified in the Vagrantfile
32
+ # * [cache_timeout] - integer specifying how long Rouster should cache status() and is_available_via_ssh?() results, default is false
33
+ # * [passthrough] - boolean of whether this is a VM or passthrough, default is false -- passthrough is not completely implemented
34
+ # * [sshkey] - the full or relative path to a SSH key used to auth to VM -- defaults to location Vagrant installs to (ENV[VAGRANT_HOME} or ]~/.vagrant.d/)
35
+ # * [sshtunnel] - boolean of whether or not to instantiate the SSH tunnel upon upping the VM, default is true
36
+ # * [sudo] - boolean of whether or not to prefix commands run in VM with 'sudo', default is true
37
+ # * [vagrantfile] - the full or relative path to the Vagrantfile to use, if not specified, will look for one in 5 directories above current location
38
+ # * [verbosity] - DEBUG (0) < INFO (1) < WARN (2) < ERROR (3) < FATAL (4)
46
39
  def initialize(opts = nil)
47
- @cache_timeout = opts[:cache_timeout].nil? ? false : opts[:cache_timeout]
48
- @logfile = opts[:logfile].nil? ? false : opts[:logfile]
49
- @name = opts[:name]
50
- @passthrough = opts[:passthrough].nil? ? false : opts[:passthrough]
51
- @retries = opts[:retries].nil? ? 0 : opts[:retries]
52
- @sshkey = opts[:sshkey]
53
- @sshtunnel = opts[:sshtunnel].nil? ? true : opts[:sshtunnel]
54
- @unittest = opts[:unittest].nil? ? false : opts[:unittest]
55
- @vagrantfile = opts[:vagrantfile].nil? ? traverse_up(Dir.pwd, 'Vagrantfile', 5) : opts[:vagrantfile]
56
- @vagrant_concurrency = opts[:vagrant_concurrency].nil? ? false : opts[:vagrant_concurrency]
57
- @vagrant_reboot = opts[:vagrant_reboot].nil? ? false : opts[:vagrant_reboot]
58
-
59
- # TODO kind of want to invert this, 0 = trace, 1 = debug, 2 = info, 3 = warning, 4 = error
60
- # could do `fixed_ordering = [4, 3, 2, 1, 0]` and use user input as index instead, so an input of 4 (which should be more verbose), yields 0
61
- if opts[:verbosity]
62
- # TODO decide how to handle this case -- currently #2 is implemented
63
- # - option 1, if passed a single integer, use that level for both loggers
64
- # - option 2, if passed a single integer, use that level for stdout, and a hardcoded level (probably INFO) to logfile
65
-
66
- # kind of want to do if opts[:verbosity].respond_to?(:[]), but for 1.87 compatability, going this way..
67
- if ! opts[:verbosity].is_a?(Array) or opts[:verbosity].is_a?(Integer)
68
- @verbosity_console = opts[:verbosity].to_i
69
- @verbosity_logfile = 2
70
- elsif opts[:verbosity].is_a?(Array)
71
- # TODO more error checking here when we are sure this is the right way to go
72
- @verbosity_console = opts[:verbosity][0].to_i
73
- @verbosity_logfile = opts[:verbosity][1].to_i
74
- @logfile = true if @logfile.eql?(false) # overriding the default setting
75
- end
40
+ @cache_timeout = opts[:cache_timeout].nil? ? false : opts[:cache_timeout]
41
+ @name = opts[:name]
42
+ @passthrough = opts[:passthrough].nil? ? false : opts[:passthrough]
43
+ @sshkey = opts[:sshkey]
44
+ @sshtunnel = opts[:sshtunnel].nil? ? true : opts[:sshtunnel]
45
+ @vagrantfile = opts[:vagrantfile].nil? ? traverse_up(Dir.pwd, 'Vagrantfile', 5) : opts[:vagrantfile]
46
+ @verbosity = opts[:verbosity].is_a?(Integer) ? opts[:verbosity] : 4
47
+
48
+ if opts.has_key?(:sudo)
49
+ @sudo = opts[:sudo]
50
+ elsif @passthrough.eql?(true)
51
+ @sudo = false
76
52
  else
77
- @verbosity_console = 3
78
- @verbosity_logfile = 2 # this is kind of arbitrary, but won't actually be created unless opts[:logfile] is also passed
53
+ @sudo = true
79
54
  end
80
55
 
81
- @ostype = nil
82
- @osversion = nil
83
-
56
+ @ostype = nil
84
57
  @output = Array.new
85
58
  @cache = Hash.new
86
59
  @deltas = Hash.new
87
60
 
88
61
  @exitcode = nil
89
- @ssh = nil # hash containing the SSH connection object
90
- @ssh_info = nil # hash containing connection information
62
+ @ssh_info = nil # will be hash containing connection information
91
63
 
92
64
  # set up logging
93
65
  require 'log4r/config'
94
66
  Log4r.define_levels(*Log4r::Log4rConfig::LogLevels)
95
67
 
96
- @logger = Log4r::Logger.new(sprintf('rouster:%s', @name))
97
- @logger.outputters << Log4r::Outputter.stderr
98
- #@log.outputters << Log4r::Outputter.stdout
68
+ @log = Log4r::Logger.new(sprintf('rouster:%s', @name))
69
+ @log.outputters = Log4r::Outputter.stderr
70
+ @log.level = @verbosity
99
71
 
100
- if @logfile
101
- @logfile = @logfile.eql?(true) ? sprintf('/tmp/rouster-%s.%s.%s.log', @name, Time.now.to_i, $$) : @logfile
102
- @logger.outputters << Log4r::FileOutputter.new(sprintf('rouster:%s', @name), :filename => @logfile, :level => @verbosity_logfile)
72
+ @log.debug('Vagrantfile and VM name validation..')
73
+ unless File.file?(@vagrantfile)
74
+ raise InternalError.new(sprintf('specified Vagrantfile [%s] does not exist', @vagrantfile))
103
75
  end
104
76
 
105
- @logger.outputters[0].level = @verbosity_console # can't set this when instantiating a .std* logger, and want the FileOutputter at a different level
77
+ raise InternalError.new() if @name.nil?
78
+ return if opts[:unittest].eql?(true) # quick return if we're a unit test
106
79
 
107
- if opts.has_key?(:sudo)
108
- @sudo = opts[:sudo]
109
- elsif @passthrough.class.eql?(Hash)
110
- @logger.debug(sprintf('passthrough without sudo specification, defaulting to false'))
111
- @sudo = false
112
- else
113
- @sudo = true
80
+ begin
81
+ self.status()
82
+ rescue Rouster::LocalExecutionError
83
+ raise InternalError.new()
114
84
  end
115
85
 
116
- if @passthrough
117
- @vagrantbinary = 'vagrant' # hacky fix to is_vagrant_running?() grepping, doesn't need to actually be in $PATH
118
- @sshtunnel = opts[:sshtunnel].nil? ? false : @sshtunnel # unless user has specified it, non-local passthroughs default to not open tunnel
119
-
120
- defaults = {
121
- :paranoid => false, # valid overrides are: false, true, :very, or :secure
122
- :ssh_sleep_ceiling => 9,
123
- :ssh_sleep_time => 10,
124
- }
125
-
126
- @passthrough = defaults.merge(@passthrough)
127
-
128
- if @passthrough.class != Hash
129
- raise ArgumentError.new('passthrough specification should be hash')
130
- elsif @passthrough[:type].nil?
131
- raise ArgumentError.new('passthrough :type must be specified, :local, :remote or :aws allowed')
132
- elsif @passthrough[:type].eql?(:local)
133
- @logger.debug('instantiating a local passthrough worker')
134
- @sshtunnel = opts[:sshtunnel].nil? ? true : opts[:sshtunnel] # override default, if local, open immediately
135
-
136
- elsif @passthrough[:type].eql?(:remote)
137
- @logger.debug('instantiating a remote passthrough worker')
138
-
139
- [:host, :user, :key].each do |r|
140
- raise ArgumentError.new(sprintf('remote passthrough requires[%s] specification', r)) if @passthrough[r].nil?
141
- end
142
-
143
- raise ArgumentError.new('remote passthrough requires valid :key specification, should be path to private half') unless File.file?(@passthrough[:key])
144
- @sshkey = @passthrough[:key] # TODO refactor so that you don't have to do this..
145
-
146
- elsif @passthrough[:type].eql?(:aws) or @passthrough[:type].eql?(:raiden)
147
- @logger.debug(sprintf('instantiating an %s passthrough worker', @passthrough[:type]))
148
-
149
- aws_defaults = {
150
- :ami => 'ami-7bdaa84b', # RHEL 6.5 x64 in us-west-2
151
- :dns_propagation_sleep => 30, # how much time to wait after ELB creation before attempting to connect
152
- :elb_cleanup => false,
153
- :key_id => ENV['AWS_ACCESS_KEY_ID'],
154
- :min_count => 1,
155
- :max_count => 1,
156
- :region => 'us-west-2',
157
- :secret_key => ENV['AWS_SECRET_ACCESS_KEY'],
158
- :size => 't1.micro',
159
- :ssh_port => 22,
160
- :user => 'ec2-user',
161
- }
162
-
163
- if @passthrough.has_key?(:ami)
164
- @logger.debug(':ami specified, will start new EC2 instance')
165
-
166
- @passthrough[:security_groups] = @passthrough[:security_groups].is_a?(Array) ? @passthrough[:security_groups] : [ @passthrough[:security_groups] ]
167
-
168
- @passthrough = aws_defaults.merge(@passthrough)
169
-
170
- [:ami, :size, :user, :region, :key, :keypair, :key_id, :secret_key, :security_groups].each do |r|
171
- raise ArgumentError.new(sprintf('AWS passthrough requires %s specification', r)) if @passthrough[r].nil?
172
- end
173
-
174
- elsif @passthrough.has_key?(:instance)
175
- @logger.debug(':instance specified, will connect to existing EC2 instance')
176
-
177
- @passthrough = aws_defaults.merge(@passthrough)
178
-
179
- if @passthrough[:type].eql?(:aws)
180
- @passthrough[:host] = self.aws_describe_instance(@passthrough[:instance])['dnsName']
181
- else
182
- @passthrough[:host] = self.find_ssh_elb(true)
183
- end
184
-
185
- [:instance, :key, :user, :host].each do |r|
186
- raise ArgumentError.new(sprintf('AWS passthrough requires [%s] specification', r)) if @passthrough[r].nil?
187
- end
188
-
189
- else
190
- raise ArgumentError.new('AWS passthrough requires either :ami or :instance specification')
191
- end
192
-
193
- raise ArgumentError.new('AWS passthrough requires valid :sshkey specification, should be path to private half') unless File.file?(@passthrough[:key])
194
- @sshkey = @passthrough[:key]
195
- elsif @passthrough[:type].eql?(:openstack)
196
- @logger.debug(sprintf('instantiating an %s passthrough worker', @passthrough[:type]))
197
- @sshkey = @passthrough[:key]
198
-
199
- ostack_defaults = {
200
- :ssh_port => 22,
201
- }
202
- @passthrough = ostack_defaults.merge(@passthrough)
203
-
204
- [:openstack_auth_url, :openstack_username, :openstack_tenant, :openstack_api_key,
205
- :key ].each do |r|
206
- raise ArgumentError.new(sprintf('OpenStack passthrough requires %s specification', r)) if @passthrough[r].nil?
207
- end
208
-
209
- if @passthrough.has_key?(:image_ref)
210
- @logger.debug(':image_ref specified, will start new Nova instance')
211
- elsif @passthrough.has_key?(:instance)
212
- @logger.debug(':instance specified, will connect to existing OpenStack instance')
213
- inst_details = self.ostack_describe_instance(@passthrough[:instance])
214
- raise ArgumentError.new(sprintf('No such instance found in OpenStack - %s', @passthrough[:instance])) if inst_details.nil?
215
- inst_details.addresses.each_key do |address_key|
216
- if defined?(inst_details.addresses[address_key].first['addr'])
217
- @passthrough[:host] = inst_details.addresses[address_key].first['addr']
218
- break
219
- end
220
- end
221
- end
86
+ @log.debug('SSH key discovery and viability tests..')
87
+ if @sshkey.nil?
88
+ if @passthrough.eql?(true)
89
+ raise InternalError.new('must specify sshkey when using a passthrough host')
222
90
  else
223
- raise ArgumentError.new(sprintf('passthrough :type [%s] unknown, allowed: :aws, :openstack, :local, :remote', @passthrough[:type]))
224
- end
225
-
226
- else
227
-
228
- @logger.debug('Vagrantfile and VM name validation..')
229
- unless File.file?(@vagrantfile)
230
- raise ArgumentError.new(sprintf('specified Vagrantfile [%s] does not exist', @vagrantfile))
231
- end
232
-
233
- raise ArgumentError.new('name of Vagrant VM not specified') if @name.nil?
234
-
235
- return if opts[:unittest].eql?(true) # quick return if we're a unit test
236
-
237
- begin
238
- @vagrantbinary = self._run('which vagrant').chomp!
239
- rescue
240
- raise ExternalError.new('vagrant not found in path')
241
- end
242
-
243
- @logger.debug('SSH key discovery and viability tests..')
244
- if @sshkey.nil?
245
91
  # ref the key from the vagrant home dir if it's been overridden
246
92
  @sshkey = sprintf('%s/insecure_private_key', ENV['VAGRANT_HOME']) if ENV['VAGRANT_HOME']
247
93
  @sshkey = sprintf('%s/.vagrant.d/insecure_private_key', ENV['HOME']) unless ENV['VAGRANT_HOME']
@@ -253,18 +99,19 @@ class Rouster
253
99
  raise InternalError.new('ssh key does not exist') unless File.file?(@sshkey)
254
100
  self.check_key_permissions(@sshkey)
255
101
  rescue => e
256
-
257
- unless self.is_passthrough? and @passthrough[:type].eql?(:local)
258
- raise InternalError.new("specified key [#{@sshkey}] has bad permissions. Vagrant exception: [#{e.message}]")
259
- end
260
-
102
+ raise InternalError.new("specified key [#{@sshkey}] has bad permissions. Vagrant exception: [#{e.message}]")
261
103
  end
262
104
 
263
105
  if @sshtunnel
264
- self.up()
106
+ unless self.status.eql?('running')
107
+ @log.info(sprintf('upping machine[%s] in order to open SSH tunnel', @name))
108
+ self.up()
109
+ end
110
+
111
+ self.connect_ssh_tunnel()
265
112
  end
266
113
 
267
- @logger.info('Rouster object successfully instantiated')
114
+ @log.info('Rouster object successfully instantiated')
268
115
  end
269
116
 
270
117
 
@@ -273,15 +120,81 @@ class Rouster
273
120
  #
274
121
  # overloaded method to return useful information about Rouster objects
275
122
  def inspect
276
- s = self.status()
277
123
  "name [#{@name}]:
278
124
  is_available_via_ssh?[#{self.is_available_via_ssh?}],
279
125
  passthrough[#{@passthrough}],
280
126
  sshkey[#{@sshkey}],
281
- status[#{s}],
127
+ status[#{self.status()}],
282
128
  sudo[#{@sudo}],
283
129
  vagrantfile[#{@vagrantfile}],
284
- verbosity console[#{@verbosity_console}] / log[#{@verbosity_logfile} - #{@logfile}]\n"
130
+ verbosity[#{@verbosity}]\n"
131
+ end
132
+
133
+ ## Vagrant methods
134
+
135
+ ##
136
+ # up
137
+ # runs `vagrant up` from the Vagrantfile path
138
+ # if :sshtunnel is passed to the object during instantiation, the tunnel is created here as well
139
+ def up
140
+ @log.info('up()')
141
+ self._run(sprintf('cd %s; vagrant up %s', File.dirname(@vagrantfile), @name))
142
+
143
+ @ssh_info = nil # in case the ssh-info has changed, a la destroy/rebuild
144
+ self.connect_ssh_tunnel() if @sshtunnel
145
+ end
146
+
147
+ ##
148
+ # destroy
149
+ # runs `vagrant destroy <name>` from the Vagrantfile path
150
+ def destroy
151
+ @log.info('destroy()')
152
+ self._run(sprintf('cd %s; vagrant destroy -f %s', File.dirname(@vagrantfile), @name))
153
+ end
154
+
155
+ ##
156
+ # status
157
+ #
158
+ # runs `vagrant status <name>` from the Vagrantfile path
159
+ # parses the status and provider out of output, but only status is returned
160
+ def status
161
+ status = nil
162
+
163
+ if @cache_timeout
164
+ if @cache.has_key?(:status)
165
+ if (Time.now.to_i - @cache[:status][:time]) < @cache_timeout
166
+ @log.debug(sprintf('using cached status[%s] from [%s]', @cache[:status][:status], @cache[:status][:time]))
167
+ return @cache[:status][:status]
168
+ end
169
+ end
170
+ end
171
+
172
+ @log.info('status()')
173
+ self._run(sprintf('cd %s; vagrant status %s', File.dirname(@vagrantfile), @name))
174
+
175
+ # else case here is handled by non-0 exit code
176
+ if self.get_output().match(/^#{@name}\s*(.*\s?\w+)\s(.+)$/)
177
+ # $1 = name, $2 = provider
178
+ status = $1
179
+ end
180
+
181
+ if @cache_timeout
182
+ @cache[:status] = Hash.new unless @cache[:status].class.eql?(Hash)
183
+ @cache[:status][:time] = Time.now.to_i
184
+ @cache[:status][:status] = status
185
+ @log.debug(sprintf('caching status[%s] at [%s]', @cache[:status][:status], @cache[:status][:time]))
186
+ end
187
+
188
+ return status
189
+ end
190
+
191
+ ##
192
+ # suspend
193
+ #
194
+ # runs `vagrant suspend <name>` from the Vagrantfile path
195
+ def suspend
196
+ @log.info('suspend()')
197
+ self._run(sprintf('cd %s; vagrant suspend %s', File.dirname(@vagrantfile), @name))
285
198
  end
286
199
 
287
200
  ## internal methods
@@ -308,28 +221,10 @@ class Rouster
308
221
  expected_exitcode = [expected_exitcode] unless expected_exitcode.class.eql?(Array) # yuck, but 2.0 no longer coerces strings into single element arrays
309
222
 
310
223
  cmd = sprintf('%s%s; echo ec[$?]', self.uses_sudo? ? 'sudo ' : '', command)
311
- @logger.info(sprintf('vm running: [%s]', cmd)) # TODO decide whether this should be changed in light of passthroughs.. 'remotely'?
312
-
313
- 0.upto(@retries) do |try|
314
- begin
315
- if self.is_passthrough? and self.passthrough[:type].eql?(:local)
316
- output = `#{cmd}`
317
- else
318
- output = @ssh.exec!(cmd)
319
- end
320
-
321
- break
322
- rescue => e
323
- @logger.error(sprintf('failed to run [%s] with [%s], attempt[%s/%s]', cmd, e, try, retries)) if self.retries > 0
324
- sleep 10 # TODO need to expose this as a variable
325
- end
224
+ @log.info(sprintf('vm running: [%s]', cmd))
326
225
 
327
- end
328
-
329
- if output.nil?
330
- output = "error gathering output, last logged output[#{self.get_output()}]"
331
- @exitcode = 256
332
- elsif output.match(/ec\[(\d+)\]/)
226
+ output = @ssh.exec!(cmd)
227
+ if output.match(/ec\[(\d+)\]/)
333
228
  @exitcode = $1.to_i
334
229
  output.gsub!(/ec\[(\d+)\]\n/, '')
335
230
  else
@@ -337,10 +232,9 @@ class Rouster
337
232
  end
338
233
 
339
234
  self.output.push(output)
340
- @logger.debug(sprintf('output: [%s]', output))
235
+ @log.debug(sprintf('output: [%s]', output))
341
236
 
342
237
  unless expected_exitcode.member?(@exitcode)
343
- # TODO technically this could be a 'LocalPassthroughExecutionError' now too if local passthrough.. should we update?
344
238
  raise RemoteExecutionError.new("output[#{output}], exitcode[#{@exitcode}], expected[#{expected_exitcode}]")
345
239
  end
346
240
 
@@ -360,7 +254,7 @@ class Rouster
360
254
  if @cache_timeout
361
255
  if @cache.has_key?(:is_available_via_ssh?)
362
256
  if (Time.now.to_i - @cache[:is_available_via_ssh?][:time]) < @cache_timeout
363
- @logger.debug(sprintf('using cached is_available_via_ssh?[%s] from [%s]', @cache[:is_available_via_ssh?][:status], @cache[:is_available_via_ssh?][:time]))
257
+ @log.debug(sprintf('using cached is_available_via_ssh?[%s] from [%s]', @cache[:is_available_via_ssh?][:status], @cache[:is_available_via_ssh?][:time]))
364
258
  return @cache[:is_available_via_ssh?][:status]
365
259
  end
366
260
  end
@@ -368,27 +262,28 @@ class Rouster
368
262
 
369
263
  if @ssh.nil? or @ssh.closed?
370
264
  begin
371
- res = self.connect_ssh_tunnel()
372
- rescue Rouster::InternalError, Net::SSH::Disconnect, Errno::ECONNREFUSED, Errno::ECONNRESET => e
265
+ self.connect_ssh_tunnel()
266
+ rescue Rouster::InternalError, Net::SSH::Disconnect => e
373
267
  res = false
374
268
  end
375
269
 
376
270
  end
377
271
 
378
- if res.nil? or res.is_a?(Net::SSH::Connection::Session)
272
+ if res.nil?
379
273
  begin
380
274
  self.run('echo functional test of SSH tunnel')
381
- res = true
382
275
  rescue
383
276
  res = false
384
277
  end
385
278
  end
386
279
 
280
+ res = true if res.nil?
281
+
387
282
  if @cache_timeout
388
283
  @cache[:is_available_via_ssh?] = Hash.new unless @cache[:is_available_via_ssh?].class.eql?(Hash)
389
284
  @cache[:is_available_via_ssh?][:time] = Time.now.to_i
390
285
  @cache[:is_available_via_ssh?][:status] = res
391
- @logger.debug(sprintf('caching is_available_via_ssh?[%s] at [%s]', @cache[:is_available_via_ssh?][:status], @cache[:is_available_via_ssh?][:time]))
286
+ @log.debug(sprintf('caching is_available_via_ssh?[%s] at [%s]', @cache[:is_available_via_ssh?][:status], @cache[:is_available_via_ssh?][:time]))
392
287
  end
393
288
 
394
289
  res
@@ -405,11 +300,10 @@ class Rouster
405
300
  h = Hash.new()
406
301
 
407
302
  if @ssh_info.class.eql?(Hash)
408
- @logger.debug('using cached SSH info')
409
303
  h = @ssh_info
410
304
  else
411
305
 
412
- res = self.vagrant(sprintf('ssh-config %s', @name))
306
+ res = self._run(sprintf('cd %s; vagrant ssh-config %s', File.dirname(@vagrantfile), @name))
413
307
 
414
308
  res.split("\n").each do |line|
415
309
  if line.match(/HostName (.*?)$/)
@@ -419,8 +313,8 @@ class Rouster
419
313
  elsif line.match(/Port (\d*?)$/)
420
314
  h[:ssh_port] = $1
421
315
  elsif line.match(/IdentityFile (.*?)$/)
316
+ # TODO what to do if the user has specified @sshkey ?
422
317
  h[:identity_file] = $1
423
- @logger.info(sprintf('vagrant specified key[%s] differs from provided[%s], will use both', @sshkey, h[:identity_file]))
424
318
  end
425
319
  end
426
320
 
@@ -437,73 +331,18 @@ class Rouster
437
331
  #
438
332
  # raises its own InternalError if the machine isn't running, otherwise returns Net::SSH connection object
439
333
  def connect_ssh_tunnel
334
+ @log.debug('opening SSH tunnel..')
440
335
 
441
- if self.is_passthrough?
442
- if @passthrough[:type].eql?(:local)
443
- @logger.debug("local passthroughs don't need ssh tunnel, shell execs are used")
444
- return false
445
- elsif @passthrough[:host].nil?
446
- @logger.info(sprintf('not attempting to connect, no known hostname for[%s]', self.passthrough))
447
- return false
448
- else
449
- ceiling = @passthrough[:ssh_sleep_ceiling]
450
- sleep_time = @passthrough[:ssh_sleep_time]
451
-
452
- 0.upto(ceiling) do |try|
453
- @logger.debug(sprintf('opening remote SSH tunnel[%s]..', @passthrough[:host]))
454
- begin
455
- @ssh = Net::SSH.start(
456
- @passthrough[:host],
457
- @passthrough[:user],
458
- :port => @passthrough[:ssh_port],
459
- :keys => [ @passthrough[:key] ], # TODO this should be @sshkey
460
- :paranoid => false
461
- )
462
- break
463
- rescue => e
464
- raise e if try.eql?(ceiling) # eventually want to throw a SocketError
465
- @logger.debug(sprintf('failed to open tunnel[%s], trying again in %ss', e.message, sleep_time))
466
- sleep sleep_time
467
- end
468
- end
469
- end
470
- @logger.debug(sprintf('successfully opened SSH tunnel to[%s]', passthrough[:host]))
471
-
336
+ if self.status.eql?('running')
337
+ self.get_ssh_info()
338
+ @ssh = Net::SSH.start(@ssh_info[:hostname], @ssh_info[:user], :port => @ssh_info[:ssh_port], :keys => [@sshkey], :paranoid => false)
472
339
  else
473
- # not a passthrough, normal connection
474
- status = self.status()
475
-
476
- if status.eql?('running')
477
- self.get_ssh_info()
478
- @logger.debug('opening VM SSH tunnel..')
479
- @ssh = Net::SSH.start(
480
- @ssh_info[:hostname],
481
- @ssh_info[:user],
482
- :port => @ssh_info[:ssh_port],
483
- :keys => [ @sshkey, @ssh_info[:identity_file] ].uniq, # try to use what the user specified first, but fall back to what vagrant says
484
- :paranoid => false
485
- )
486
- else
487
- # TODO will we ever hit this? or will we be thrown first?
488
- raise InternalError.new(sprintf('VM is not running[%s], unable open SSH tunnel', status))
489
- end
340
+ raise InternalError.new('VM is not running, unable open SSH tunnel')
490
341
  end
491
342
 
492
343
  @ssh
493
344
  end
494
345
 
495
- ##
496
- # disconnect_ssh_tunnel
497
- #
498
- # shuts down the persistent Net::SSH tunnel
499
- #
500
- def disconnect_ssh_tunnel
501
- @logger.debug('closing SSH tunnel..')
502
-
503
- @ssh.shutdown! unless @ssh.nil?
504
- @ssh = nil
505
- end
506
-
507
346
  ##
508
347
  # os_type
509
348
  #
@@ -514,61 +353,28 @@ class Rouster
514
353
  return @ostype
515
354
  end
516
355
 
517
- res = :invalid
356
+ res = nil
357
+ uname = self.run('uname -a')
518
358
 
519
- Rouster.os_files.each_pair do |os, f|
520
- [ f ].flatten.each do |candidate|
521
- if self.is_file?(candidate)
522
- next if candidate.eql?('/etc/os-release') and ! self.is_in_file?(candidate, os.to_s, 'i') # CentOS detection
523
- @logger.debug(sprintf('determined OS to be[%s] via[%s]', os, candidate))
524
- res = os
359
+ case uname
360
+ when /Darwin/i
361
+ res = :osx
362
+ when /Sun|Solaris/i
363
+ res =:solaris
364
+ when /Ubuntu/i
365
+ res = :ubuntu
366
+ else
367
+ if self.is_file?('/etc/redhat-release')
368
+ res = :redhat
369
+ else
370
+ res = nil
525
371
  end
526
- end
527
- break unless res.eql?(:invalid)
528
372
  end
529
373
 
530
- @logger.error(sprintf('unable to determine OS, looking for[%s]', Rouster.os_files)) if res.eql?(:invalid)
531
-
532
374
  @ostype = res
533
375
  res
534
376
  end
535
377
 
536
- ##
537
- # os_version
538
- #
539
- #
540
- def os_version(os_type)
541
- return @osversion if @osversion
542
-
543
- res = :invalid
544
-
545
- [ Rouster.os_files[os_type] ].flatten.each do |candidate|
546
- if self.is_file?(candidate)
547
- next if candidate.eql?('/etc/os-release') and ! self.is_in_file?(candidate, os_type.to_s, 'i') # CentOS detection
548
- contents = self.run(sprintf('cat %s', candidate))
549
- if os_type.eql?(:ubuntu)
550
- version = $1 if contents.match(/.*VERSION\="(\d+\.\d+).*"/) # VERSION="13.10, Saucy Salamander"
551
- res = version unless version.nil?
552
- elsif os_type.eql?(:rhel)
553
- version = $1 if contents.match(/.*VERSION\="(\d+)"/) # VERSION="7 (Core)"
554
- version = $1 if version.nil? and contents.match(/.*(\d+.\d+)/) # CentOS release 6.4 (Final)
555
- res = version unless version.nil?
556
- elsif os_type.eql?(:osx)
557
- version = $1 if contents.match(/<key>ProductVersion<\/key>.*<string>(.*)<\/string>/m) # <key>ProductVersion</key>\n <string>10.12.1</string>
558
- res = version unless version.nil?
559
- end
560
-
561
- end
562
- break unless res.eql?(:invalid)
563
- end
564
-
565
- @logger.error(sprintf('unable to determine OS version, looking for[%s]', Rouster.os_files[os_type])) if res.eql?(:invalid)
566
-
567
- @osversion = res
568
-
569
- res
570
- end
571
-
572
378
  ##
573
379
  # get
574
380
  #
@@ -579,13 +385,13 @@ class Rouster
579
385
  # * [local_file] - full or relative path (based on $PWD) of file to download to
580
386
  #
581
387
  # if no local_file is specified, will be downloaded to $PWD with the same shortname as it had in the VM
582
- #
583
- # returns true on successful download, false if the file DNE and raises a FileTransferError.. well, you know
584
388
  def get(remote_file, local_file=nil)
585
389
  # TODO what happens when we pass a wildcard as remote_file?
586
390
 
587
391
  local_file = local_file.nil? ? File.basename(remote_file) : local_file
588
- @logger.debug(sprintf('scp from VM[%s] to host[%s]', remote_file, local_file))
392
+ @log.debug(sprintf('scp from VM[%s] to host[%s]', remote_file, local_file))
393
+
394
+ # TODO should we do a self.file?(remote_file) test before trying to download?
589
395
 
590
396
  begin
591
397
  @ssh.scp.download!(remote_file, local_file)
@@ -593,7 +399,6 @@ class Rouster
593
399
  raise FileTransferError.new(sprintf('unable to get[%s], exception[%s]', remote_file, e.message()))
594
400
  end
595
401
 
596
- return true
597
402
  end
598
403
 
599
404
  ##
@@ -606,7 +411,7 @@ class Rouster
606
411
  # * [remote_file] - full or relative path (based on ~vagrant) of filename to upload to
607
412
  def put(local_file, remote_file=nil)
608
413
  remote_file = remote_file.nil? ? File.basename(local_file) : remote_file
609
- @logger.debug(sprintf('scp from host[%s] to VM[%s]', local_file, remote_file))
414
+ @log.debug(sprintf('scp from host[%s] to VM[%s]', local_file, remote_file))
610
415
 
611
416
  raise FileTransferError.new(sprintf('unable to put[%s], local file does not exist', local_file)) unless File.file?(local_file)
612
417
 
@@ -616,7 +421,6 @@ class Rouster
616
421
  raise FileTransferError.new(sprintf('unable to put[%s], exception[%s]', local_file, e.message()))
617
422
  end
618
423
 
619
- return true
620
424
  end
621
425
 
622
426
  ##
@@ -624,7 +428,7 @@ class Rouster
624
428
  #
625
429
  # convenience getter for @passthrough truthiness
626
430
  def is_passthrough?
627
- @passthrough.class.eql?(Hash)
431
+ self.passthrough.eql?(true)
628
432
  end
629
433
 
630
434
  ##
@@ -632,7 +436,7 @@ class Rouster
632
436
  #
633
437
  # convenience getter for @sudo truthiness
634
438
  def uses_sudo?
635
- @sudo.eql?(true)
439
+ self.sudo.eql?(true)
636
440
  end
637
441
 
638
442
  ##
@@ -640,7 +444,7 @@ class Rouster
640
444
  #
641
445
  # destroy and then up the machine in question
642
446
  def rebuild
643
- @logger.debug('rebuild()')
447
+ @log.debug('rebuild()')
644
448
  self.destroy
645
449
  self.up
646
450
  end
@@ -652,49 +456,37 @@ class Rouster
652
456
  #
653
457
  # parameters
654
458
  # * [wait] - number of seconds to wait until is_available_via_ssh?() returns true before assuming failure
655
- def restart(wait=nil, expected_exitcodes = [0])
656
- @logger.debug('restart()')
459
+ def restart(wait=nil)
460
+ @log.debug('restart()')
657
461
 
658
- if self.is_passthrough? and self.passthrough[:type].eql?(:local)
659
- @logger.warn(sprintf('intercepted [restart] sent to a local passthrough, no op'))
462
+ if self.is_passthrough? and self.passthrough.eql?(local)
463
+ @log.warn(sprintf('intercepted [restart] sent to a local passthrough, no op'))
660
464
  return nil
661
465
  end
662
466
 
663
- if @vagrant_reboot
664
- # leading vagrant handle this through 'reload --no-provision'
665
- self.reload
666
- else
667
- # trying to do it ourselves
668
- case os_type
669
- when :osx
670
- self.run('shutdown -r now', expected_exitcodes)
671
- when :rhel, :ubuntu
672
- if os_type.eql?(:rhel) and os_version(os_type).match(/7/)
673
- self.run('shutdown --halt --reboot now', expected_exitcodes << 256)
674
- else
675
- self.run('shutdown -rf now')
676
- end
677
- when :solaris
678
- self.run('shutdown -y -i5 -g0', expected_exitcodes)
679
- else
680
- raise InternalError.new(sprintf('unsupported OS[%s]', @ostype))
681
- end
467
+ case os_type
468
+ when :osx
469
+ self.run('shutdown -r now')
470
+ when :redhat, :ubuntu
471
+ self.run('/sbin/shutdown -rf now')
472
+ when :solaris
473
+ self.run('shutdown -y -i5 -g0')
474
+ else
475
+ raise InternalError.new(sprintf('unsupported OS[%s]', @ostype))
682
476
  end
683
477
 
684
- @ssh, @ssh_info = nil # severing the SSH tunnel, getting ready in case this box is brought back up on a different port
685
-
686
478
  if wait
687
479
  inc = wait.to_i / 10
688
- 0.upto(9) do |e|
689
- @logger.debug(sprintf('waiting for reboot: round[%s], step[%s], total[%s]', e, inc, wait))
690
- return true if self.is_available_via_ssh?()
480
+ 0..wait.each do |e|
481
+ @log.debug(sprintf('waiting for reboot: round[%s], step[%s], total[%s]', e, inc, wait))
482
+ true if self.is_available_via_ssh?()
691
483
  sleep inc
692
484
  end
693
485
 
694
- return false
486
+ false
695
487
  end
696
488
 
697
- return true
489
+ @ssh, @ssh_info = nil, nil
698
490
  end
699
491
 
700
492
  ##
@@ -708,22 +500,22 @@ class Rouster
708
500
  # parameters
709
501
  # * <command> - command to be run
710
502
  def _run(command)
711
- tmp_file = sprintf('/tmp/rouster-cmd_output.%s.%s', Time.now.to_i, $$)
503
+ tmp_file = sprintf('/tmp/rouster.%s.%s', Time.now.to_i, $$)
712
504
  cmd = sprintf('%s > %s 2> %s', command, tmp_file, tmp_file) # this is a holdover from Salesforce::Vagrant, can we use '2&>1' here?
713
505
  res = `#{cmd}` # what does this actually hold?
714
506
 
715
- @logger.info(sprintf('host running: [%s]', cmd))
507
+ @log.info(sprintf('host running: [%s]', cmd))
716
508
 
717
509
  output = File.read(tmp_file)
718
510
  File.delete(tmp_file) or raise InternalError.new(sprintf('unable to delete [%s]: %s', tmp_file, $!))
719
511
 
720
- self.output.push(output)
721
- @logger.debug(sprintf('output: [%s]', output))
722
-
723
512
  unless $?.success?
724
513
  raise LocalExecutionError.new(sprintf('command [%s] exited with code [%s], output [%s]', cmd, $?.to_i(), output))
725
514
  end
726
515
 
516
+ self.output.push(output)
517
+ @log.debug(sprintf('output: [%s]', output))
518
+
727
519
  @exitcode = $?.to_i()
728
520
  output
729
521
  end
@@ -765,9 +557,10 @@ class Rouster
765
557
  # * [filename] - filename you are looking for
766
558
  # * [levels] - number of directory levels to examine, default is 10
767
559
  def traverse_up(startdir=Dir.pwd, filename=nil, levels=10)
560
+ # TODO not sure this signature is exactly right..
768
561
  raise InternalError.new('must specify a filename') if filename.nil?
769
562
 
770
- @logger.debug(sprintf('traverse_up() looking for [%s] in [%s], up to [%s] levels', filename, startdir, levels)) unless @logger.nil?
563
+ @log.debug(sprintf('traverse_up() looking for [%s] in [%s], up to [%s] levels', filename, startdir, levels)) unless @log.nil?
771
564
 
772
565
  dirs = startdir.split('/')
773
566
  count = 0
@@ -796,11 +589,6 @@ class Rouster
796
589
  def check_key_permissions(key, fix=false)
797
590
  allowed_modes = ['0400', '0600']
798
591
 
799
- if key.match(/\.pub$/)
800
- # if this is the public half of the key, be more permissive
801
- allowed_modes << '0644'
802
- end
803
-
804
592
  raw = self._run(sprintf('ls -l %s', key))
805
593
  perms = self.parse_ls_string(raw)
806
594
 
@@ -825,15 +613,6 @@ class Rouster
825
613
  nil
826
614
  end
827
615
 
828
- def self.os_files
829
- {
830
- :ubuntu => '/etc/os-release', # debian too
831
- :solaris => '/etc/release',
832
- :rhel => ['/etc/os-release', '/etc/redhat-release'], # and centos
833
- :osx => '/System/Library/CoreServices/SystemVersion.plist',
834
- }
835
- end
836
-
837
616
  end
838
617
 
839
618
  class Object