rouster 0.7 → 0.41

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.
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