rouster 0.53 → 0.57

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76526d9f9fbaca98b3ea2bfad51a769f67df6647
4
- data.tar.gz: 6f9bc1a95bec7be30e00eccb1cd0ca28c6d24a72
3
+ metadata.gz: ca887947fd8eb61d347ebb19f562d4ab6f79cf79
4
+ data.tar.gz: 424047f4ad62c0b279baa71d503045096416b37f
5
5
  SHA512:
6
- metadata.gz: 345f5b6e4ef69a8a023e4bf3aebbb517e69a0ff7eee1f95b330bb1c373becfa1c231931f5c189099ec44acc355efe5a23b1c07cf057dc9cd6a297c7651343391
7
- data.tar.gz: 92be5fcb79d74870bea301a722a539459656a89b9a7c139dba1c02e5fcbff5d12207f6dab4720952be5e58ab6606e27781885dfea29ffef1a117de757e713e56
6
+ metadata.gz: 90856a137433d2c34a2f2f7e3977c748bb8457f96049c0111b650ab6f5e0520380f74406b20b7a274f01bc8c8b1341b5d0c6fd345cec5960db718b76c6b9a55c
7
+ data.tar.gz: e6f9d8b1400e684cf89451a4ac2d46ace2f3642927a7b70cb666ed03e2475b250c45a82c68174f8ce1cebf252ec2b2ccb07546b69193ecbf2568d3cad3858e18
data/README.md CHANGED
@@ -24,8 +24,11 @@ The first implementation of Rouster was in Perl, called [Salesforce::Vagrant](ht
24
24
 
25
25
  * [Ruby](http://rubylang.org), version 2.0+ (best attempt made to support 1.8.7 and 1.9.3 as well)
26
26
  * [Vagrant](http://vagrantup.com), version 1.0.5+
27
-
28
- Note: Vagrant itself requires VirtualBox or VMWare Fusion (1.0.3+)
27
+ * Gems
28
+ * json
29
+ * log4r
30
+ * net-scp
31
+ * net-ssh
29
32
 
30
33
  Note: Rouster should work exactly the same on Windows as it does on \*nix and OSX (minus rouster/deltas.rb functionality, at least currently),
31
34
  but no real testing has been done to confirm this. Please file issues as appropriate.
@@ -0,0 +1,71 @@
1
+ require sprintf('%s/../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
2
+
3
+ require 'rouster'
4
+ require 'rouster/puppet'
5
+ require 'rouster/tests'
6
+
7
+ verbosity = ENV['VERBOSE'].nil? ? 4 : 0
8
+
9
+ # .inspect of this is blank for sshkey and status, looks ugly, but is ~accurate.. fix this?
10
+ local = Rouster.new(
11
+ :name => 'local',
12
+ :sudo => false,
13
+ :passthrough => { :type => :local },
14
+ :verbosity => verbosity,
15
+ )
16
+
17
+ remote = Rouster.new(
18
+ :name => 'remote',
19
+ :sudo => false,
20
+ :passthrough => {
21
+ :type => :remote,
22
+ :host => `hostname`.chomp, # yep, the remote is actually local.. perhaps the right :type would be 'ssh' vs 'shellout' instead..
23
+ :user => ENV['USER'],
24
+ :key => sprintf('%s/.ssh/id_dsa', ENV['HOME']),
25
+ },
26
+ :verbosity => verbosity,
27
+ )
28
+
29
+ sudo = Rouster.new(
30
+ :name => 'sudo',
31
+ :sudo => true,
32
+ :passthrough => { :type => :local },
33
+ :verbosity => verbosity,
34
+ )
35
+
36
+ vagrant = Rouster.new(
37
+ :name => 'ppm',
38
+ :sudo => true,
39
+ :verbosity => verbosity,
40
+ )
41
+
42
+ workers = [ local, remote, vagrant ]
43
+
44
+ workers = [vagrant]
45
+
46
+ workers.each do |r|
47
+ p r
48
+
49
+ ## vagrant command testing
50
+ r.up()
51
+ r.suspend()
52
+ #r.destroy()
53
+ r.up()
54
+
55
+ p r.status() # why is this giving us nil after initial call? want to blame caching, but not sure
56
+
57
+ r.is_vagrant_running?()
58
+ r.sandbox_available?()
59
+
60
+ if r.sandbox_available?()
61
+ r.sandbox_on()
62
+ r.sandbox_off()
63
+ r.sandbox_rollback()
64
+ r.sandbox_commit()
65
+ end
66
+
67
+ p r.run('echo foo')
68
+
69
+ end
70
+
71
+ exit
data/lib/rouster.rb CHANGED
@@ -7,11 +7,12 @@ 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'
10
11
 
11
12
  class Rouster
12
13
 
13
14
  # sporadically updated version number
14
- VERSION = 0.53
15
+ VERSION = 0.57
15
16
 
16
17
  # custom exceptions -- what else do we want them to include/do?
17
18
  class ArgumentError < StandardError; end # thrown by methods that take parameters from users
@@ -20,36 +21,65 @@ class Rouster
20
21
  class ExternalError < StandardError; end # thrown when external dependencies do not respond as expected
21
22
  class LocalExecutionError < StandardError; end # thrown by _run()
22
23
  class RemoteExecutionError < StandardError; end # thrown by run()
24
+ class PassthroughError < StandardError; end # thrown by anything Passthrough related (mostly vagrant.rb)
23
25
  class SSHConnectionError < StandardError; end # thrown by available_via_ssh() -- and potentially _run()
24
26
 
25
- attr_accessor :facts, :sudo, :verbosity
26
- attr_reader :cache, :cache_timeout, :deltas, :exitcode, :log, :name, :output, :passthrough, :sshkey, :unittest, :vagrantfile
27
+ attr_accessor :facts
28
+ attr_reader :cache, :cache_timeout, :deltas, :exitcode, :logger, :name, :output, :passthrough, :retries, :sshkey, :unittest, :vagrantbinary, :vagrantfile
27
29
 
28
30
  ##
29
31
  # initialize - object instantiation
30
32
  #
31
33
  # parameters
32
- # * <name> - the name of the VM as specified in the Vagrantfile
33
- # * [cache_timeout] - integer specifying how long Rouster should cache status() and is_available_via_ssh?() results, default is false
34
- # * [passthrough] - boolean of whether this is a VM or passthrough, default is false -- passthrough is not completely implemented
35
- # * [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/)
36
- # * [sshtunnel] - boolean of whether or not to instantiate the SSH tunnel upon upping the VM, default is true
37
- # * [sudo] - boolean of whether or not to prefix commands run in VM with 'sudo', default is true
38
- # * [vagrantfile] - the full or relative path to the Vagrantfile to use, if not specified, will look for one in 5 directories above current location
39
- # * [verbosity] - DEBUG (0) < INFO (1) < WARN (2) < ERROR (3) < FATAL (4)
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
+ # * [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)
40
45
  def initialize(opts = nil)
41
- @cache_timeout = opts[:cache_timeout].nil? ? false : opts[:cache_timeout]
42
- @name = opts[:name]
43
- @passthrough = opts[:passthrough].nil? ? false : opts[:passthrough]
44
- @sshkey = opts[:sshkey]
45
- @sshtunnel = opts[:sshtunnel].nil? ? true : opts[:sshtunnel]
46
- @unittest = opts[:unittest].nil? ? false : opts[:unittest]
47
- @vagrantfile = opts[:vagrantfile].nil? ? traverse_up(Dir.pwd, 'Vagrantfile', 5) : opts[:vagrantfile]
48
- @verbosity = opts[:verbosity].is_a?(Integer) ? opts[:verbosity] : 4
46
+ @cache_timeout = opts[:cache_timeout].nil? ? false : opts[:cache_timeout]
47
+ @logfile = opts[:logfile].nil? ? false : opts[:logfile]
48
+ @name = opts[:name]
49
+ @passthrough = opts[:passthrough].nil? ? false : opts[:passthrough]
50
+ @retries = opts[:retries].nil? ? 0 : opts[:retries]
51
+ @sshkey = opts[:sshkey]
52
+ @sshtunnel = opts[:sshtunnel].nil? ? true : opts[:sshtunnel]
53
+ @unittest = opts[:unittest].nil? ? false : opts[:unittest]
54
+ @vagrantfile = opts[:vagrantfile].nil? ? traverse_up(Dir.pwd, 'Vagrantfile', 5) : opts[:vagrantfile]
55
+ @vagrant_concurrency = opts[:vagrant_concurrency].nil? ? false : opts[:vagrant_concurrency]
56
+
57
+ # TODO kind of want to invert this, 0 = trace, 1 = debug, 2 = info, 3 = warning, 4 = error
58
+ # 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
59
+ if opts[:verbosity]
60
+ # TODO decide how to handle this case -- currently #2 is implemented
61
+ # - option 1, if passed a single integer, use that level for both loggers
62
+ # - option 2, if passed a single integer, use that level for stdout, and a hardcoded level (probably INFO) to logfile
63
+
64
+ # kind of want to do if opts[:verbosity].respond_to?(:[]), but for 1.87 compatability, going this way..
65
+ if ! opts[:verbosity].is_a?(Array) or opts[:verbosity].is_a?(Integer)
66
+ @verbosity_console = opts[:verbosity].to_i
67
+ @verbosity_logfile = 2
68
+ elsif opts[:verbosity].is_a?(Array)
69
+ # TODO more error checking here when we are sure this is the right way to go
70
+ @verbosity_console = opts[:verbosity][0].to_i
71
+ @verbosity_logfile = opts[:verbosity][1].to_i
72
+ @logfile = true if @logfile.eql?(false) # overriding the default setting
73
+ end
74
+ else
75
+ @verbosity_console = 3
76
+ @verbosity_logfile = 2 # this is kind of arbitrary, but won't actually be created unless opts[:logfile] is also passed
77
+ end
49
78
 
50
79
  if opts.has_key?(:sudo)
51
80
  @sudo = opts[:sudo]
52
- elsif @passthrough.eql?(true)
81
+ elsif @passthrough.class.eql?(Hash)
82
+ # TODO say something here.. or maybe check to see if our user has passwordless sudo?
53
83
  @sudo = false
54
84
  else
55
85
  @sudo = true
@@ -68,18 +98,61 @@ class Rouster
68
98
  require 'log4r/config'
69
99
  Log4r.define_levels(*Log4r::Log4rConfig::LogLevels)
70
100
 
71
- @log = Log4r::Logger.new(sprintf('rouster:%s', @name))
72
- @log.outputters = Log4r::Outputter.stderr
73
- @log.level = @verbosity
101
+ @logger = Log4r::Logger.new(sprintf('rouster:%s', @name))
102
+ @logger.outputters << Log4r::Outputter.stderr
103
+ #@log.outputters << Log4r::Outputter.stdout
104
+
105
+ if @logfile
106
+ @logfile = @logfile.eql?(true) ? sprintf('/tmp/rouster-%s.%s.%s.log', @name, Time.now.to_i, $$) : @logfile
107
+ @logger.outputters << Log4r::FileOutputter.new(sprintf('rouster:%s', @name), :filename => @logfile, :level => @verbosity_logfile)
108
+ end
109
+
110
+ @logger.outputters[0].level = @verbosity_console # can't set this when instantiating a .std* logger, and want the FileOutputter at a different level
111
+
112
+ if @passthrough
113
+ # TODO do better about informing of required specifications, maybe point them to an URL?
114
+ @vagrantbinary = 'vagrant' # hacky fix to is_vagrant_running?() grepping, doesn't need to actually be in $PATH
115
+ if @passthrough.class != Hash
116
+ raise ArgumentError.new('passthrough specification should be hash')
117
+ elsif @passthrough[:type].nil?
118
+ raise ArgumentError.new('passthrough :type must be specified, :local or :remote allowed')
119
+ elsif @passthrough[:type].eql?(:local)
120
+ @sshtunnel = false
121
+ @logger.debug('instantiating a local passthrough worker')
122
+ elsif @passthrough[:type].eql?(:remote)
123
+ raise ArgumentError.new('remote passthrough requires :host specification') if @passthrough[:host].nil?
124
+ raise ArgumentError.new('remote passthrough requires :user specification') if @passthrough[:user].nil?
125
+ raise ArgumentError.new('remote passthrough requires :key specification') if @passthrough[:key].nil?
126
+ raise ArgumentError.new('remote passthrough requires valid :key specification, should be path to private half') unless File.file?(@passthrough[:key])
127
+ @sshkey = @passthrough[:key] # TODO refactor so that you don't have to do this..
128
+ @logger.debug('instantiating a remote passthrough worker')
129
+ else
130
+ raise ArgumentError.new(sprintf('passthrough :type [%s] unknown, allowed: :local, :remote', @passthrough[:type]))
131
+ end
132
+ else
74
133
 
75
- @log.debug('Vagrantfile and VM name validation..')
76
- unless File.file?(@vagrantfile)
77
- raise InternalError.new(sprintf('specified Vagrantfile [%s] does not exist', @vagrantfile))
78
- end
134
+ @logger.debug('Vagrantfile and VM name validation..')
135
+ unless File.file?(@vagrantfile)
136
+ raise ArgumentError.new(sprintf('specified Vagrantfile [%s] does not exist', @vagrantfile))
137
+ end
79
138
 
80
- raise InternalError.new('name of Vagrant VM not specified') if @name.nil?
139
+ raise ArgumentError.new('name of Vagrant VM not specified') if @name.nil?
81
140
 
82
- return if opts[:unittest].eql?(true) # quick return if we're a unit test
141
+ return if opts[:unittest].eql?(true) # quick return if we're a unit test
142
+
143
+ begin
144
+ @vagrantbinary = self._run('which vagrant').chomp!
145
+ rescue
146
+ raise ExternalError.new('vagrant not found in path')
147
+ end
148
+
149
+ @logger.debug('SSH key discovery and viability tests..')
150
+ if @sshkey.nil?
151
+ # ref the key from the vagrant home dir if it's been overridden
152
+ @sshkey = sprintf('%s/insecure_private_key', ENV['VAGRANT_HOME']) if ENV['VAGRANT_HOME']
153
+ @sshkey = sprintf('%s/.vagrant.d/insecure_private_key', ENV['HOME']) unless ENV['VAGRANT_HOME']
154
+ end
155
+ end
83
156
 
84
157
  # this is breaking test/functional/test_caching.rb test_ssh_caching (if the VM was not running when the test started)
85
158
  # it slows down object instantiation, but is a good test to ensure the machine name is valid..
@@ -89,36 +162,23 @@ class Rouster
89
162
  raise InternalError.new(sprintf('caught non-0 exitcode from status(): %s', e.message))
90
163
  end
91
164
 
92
- begin
93
- self._run('which vagrant')
94
- rescue
95
- raise ExternalError.new('vagrant not found in path')
96
- end
97
-
98
- @log.debug('SSH key discovery and viability tests..')
99
- if @sshkey.nil?
100
- if @passthrough.eql?(true)
101
- raise InternalError.new('must specify sshkey when using a passthrough host')
102
- else
103
- # ref the key from the vagrant home dir if it's been overridden
104
- @sshkey = sprintf('%s/insecure_private_key', ENV['VAGRANT_HOME']) if ENV['VAGRANT_HOME']
105
- @sshkey = sprintf('%s/.vagrant.d/insecure_private_key', ENV['HOME']) unless ENV['VAGRANT_HOME']
106
- end
107
- end
108
-
109
165
  begin
110
166
  raise InternalError.new('ssh key not specified') if @sshkey.nil?
111
167
  raise InternalError.new('ssh key does not exist') unless File.file?(@sshkey)
112
168
  self.check_key_permissions(@sshkey)
113
169
  rescue => e
114
- raise InternalError.new("specified key [#{@sshkey}] has bad permissions. Vagrant exception: [#{e.message}]")
170
+
171
+ unless self.is_passthrough? and @passthrough[:type].eql?(:local)
172
+ raise InternalError.new("specified key [#{@sshkey}] has bad permissions. Vagrant exception: [#{e.message}]")
173
+ end
174
+
115
175
  end
116
176
 
117
177
  if @sshtunnel
118
178
  self.up()
119
179
  end
120
180
 
121
- @log.info('Rouster object successfully instantiated')
181
+ @logger.info('Rouster object successfully instantiated')
122
182
  end
123
183
 
124
184
 
@@ -127,86 +187,15 @@ class Rouster
127
187
  #
128
188
  # overloaded method to return useful information about Rouster objects
129
189
  def inspect
190
+ s = self.status()
130
191
  "name [#{@name}]:
131
192
  is_available_via_ssh?[#{self.is_available_via_ssh?}],
132
193
  passthrough[#{@passthrough}],
133
194
  sshkey[#{@sshkey}],
134
- status[#{self.status()}],
195
+ status[#{s}],
135
196
  sudo[#{@sudo}],
136
197
  vagrantfile[#{@vagrantfile}],
137
- verbosity[#{@verbosity}]\n"
138
- end
139
-
140
- ## Vagrant methods
141
-
142
- ##
143
- # up
144
- # runs `vagrant up` from the Vagrantfile path
145
- # if :sshtunnel is passed to the object during instantiation, the tunnel is created here as well
146
- def up
147
- @log.info('up()')
148
- self.vagrant(sprintf('up %s', @name))
149
-
150
- @ssh_info = nil # in case the ssh-info has changed, a la destroy/rebuild
151
- self.connect_ssh_tunnel() if @sshtunnel
152
- end
153
-
154
- ##
155
- # destroy
156
- # runs `vagrant destroy <name>` from the Vagrantfile path
157
- def destroy
158
- @log.info('destroy()')
159
- disconnect_ssh_tunnel
160
- self.vagrant(sprintf('destroy -f %s', @name))
161
- end
162
-
163
- ##
164
- # status
165
- #
166
- # runs `vagrant status <name>` from the Vagrantfile path
167
- # parses the status and provider out of output, but only status is returned
168
- def status
169
- status = nil
170
-
171
- if @cache_timeout
172
- if @cache.has_key?(:status)
173
- if (Time.now.to_i - @cache[:status][:time]) < @cache_timeout
174
- @log.debug(sprintf('using cached status[%s] from [%s]', @cache[:status][:status], @cache[:status][:time]))
175
- return @cache[:status][:status]
176
- end
177
- end
178
- end
179
-
180
- @log.info('status()')
181
- self.vagrant(sprintf('status %s', @name))
182
-
183
- # else case here is handled by non-0 exit code
184
- if self.get_output().match(/^#{@name}\s*(.*\s?\w+)\s\((.+)\)$/)
185
- # vagrant 1.2+, $1 = status, $2 = provider
186
- status = $1
187
- elsif self.get_output().match(/^#{@name}\s+(.+)$/)
188
- # vagrant 1.2-, $1 = status
189
- status = $1
190
- end
191
-
192
- if @cache_timeout
193
- @cache[:status] = Hash.new unless @cache[:status].class.eql?(Hash)
194
- @cache[:status][:time] = Time.now.to_i
195
- @cache[:status][:status] = status
196
- @log.debug(sprintf('caching status[%s] at [%s]', @cache[:status][:status], @cache[:status][:time]))
197
- end
198
-
199
- return status
200
- end
201
-
202
- ##
203
- # suspend
204
- #
205
- # runs `vagrant suspend <name>` from the Vagrantfile path
206
- def suspend
207
- @log.info('suspend()')
208
- disconnect_ssh_tunnel()
209
- self.vagrant(sprintf('suspend %s', @name))
198
+ verbosity console[#{@verbosity_console}] / log[#{@verbosity_logfile} - #{@logfile}]\n"
210
199
  end
211
200
 
212
201
  ## internal methods
@@ -233,10 +222,28 @@ class Rouster
233
222
  expected_exitcode = [expected_exitcode] unless expected_exitcode.class.eql?(Array) # yuck, but 2.0 no longer coerces strings into single element arrays
234
223
 
235
224
  cmd = sprintf('%s%s; echo ec[$?]', self.uses_sudo? ? 'sudo ' : '', command)
236
- @log.info(sprintf('vm running: [%s]', cmd))
225
+ @logger.info(sprintf('vm running: [%s]', cmd)) # TODO decide whether this should be changed in light of passthroughs.. 'remotely'?
237
226
 
238
- output = @ssh.exec!(cmd)
239
- if output.match(/ec\[(\d+)\]/)
227
+ 0.upto(@retries) do |try|
228
+ begin
229
+ if self.is_passthrough? and self.passthrough[:type].eql?(:local)
230
+ output = `#{cmd}`
231
+ else
232
+ output = @ssh.exec!(cmd)
233
+ end
234
+
235
+ break
236
+ rescue => e
237
+ @logger.error(sprintf('failed to run [%s] with [%s], attempt[%s/%s]', cmd, e, try, retries)) if self.retries > 0
238
+ sleep 10 # TODO need to expose this as a variable
239
+ end
240
+
241
+ end
242
+
243
+ if output.nil?
244
+ output = "error gathering output, last logged output[#{self.get_output()}]"
245
+ @exitcode = 256
246
+ elsif output.match(/ec\[(\d+)\]/)
240
247
  @exitcode = $1.to_i
241
248
  output.gsub!(/ec\[(\d+)\]\n/, '')
242
249
  else
@@ -244,9 +251,10 @@ class Rouster
244
251
  end
245
252
 
246
253
  self.output.push(output)
247
- @log.debug(sprintf('output: [%s]', output))
254
+ @logger.debug(sprintf('output: [%s]', output))
248
255
 
249
256
  unless expected_exitcode.member?(@exitcode)
257
+ # TODO technically this could be a 'LocalPassthroughExecutionError' now too if local passthrough.. should we update?
250
258
  raise RemoteExecutionError.new("output[#{output}], exitcode[#{@exitcode}], expected[#{expected_exitcode}]")
251
259
  end
252
260
 
@@ -266,7 +274,7 @@ class Rouster
266
274
  if @cache_timeout
267
275
  if @cache.has_key?(:is_available_via_ssh?)
268
276
  if (Time.now.to_i - @cache[:is_available_via_ssh?][:time]) < @cache_timeout
269
- @log.debug(sprintf('using cached is_available_via_ssh?[%s] from [%s]', @cache[:is_available_via_ssh?][:status], @cache[:is_available_via_ssh?][:time]))
277
+ @logger.debug(sprintf('using cached is_available_via_ssh?[%s] from [%s]', @cache[:is_available_via_ssh?][:status], @cache[:is_available_via_ssh?][:time]))
270
278
  return @cache[:is_available_via_ssh?][:status]
271
279
  end
272
280
  end
@@ -295,86 +303,12 @@ class Rouster
295
303
  @cache[:is_available_via_ssh?] = Hash.new unless @cache[:is_available_via_ssh?].class.eql?(Hash)
296
304
  @cache[:is_available_via_ssh?][:time] = Time.now.to_i
297
305
  @cache[:is_available_via_ssh?][:status] = res
298
- @log.debug(sprintf('caching is_available_via_ssh?[%s] at [%s]', @cache[:is_available_via_ssh?][:status], @cache[:is_available_via_ssh?][:time]))
306
+ @logger.debug(sprintf('caching is_available_via_ssh?[%s] at [%s]', @cache[:is_available_via_ssh?][:status], @cache[:is_available_via_ssh?][:time]))
299
307
  end
300
308
 
301
309
  res
302
310
  end
303
311
 
304
- ##
305
- # sandbox_available?
306
- #
307
- # returns true or false after attempting to find out if the sandbox
308
- # subcommand is available
309
- def sandbox_available?
310
- if @cache.has_key?(:sandbox_available?)
311
- @log.debug(sprintf('using cached sandbox_available?[%s]', @cache[:sandbox_available?]))
312
- return @cache[:sandbox_available?]
313
- end
314
-
315
- @log.info('sandbox_available()')
316
- self._run(sprintf('cd %s; vagrant', File.dirname(@vagrantfile))) # calling 'vagrant' without parameters to determine available faces
317
-
318
- sandbox_available = false
319
- if self.get_output().match(/^\s+sandbox$/)
320
- sandbox_available = true
321
- end
322
-
323
- @cache[:sandbox_available?] = sandbox_available
324
- @log.debug(sprintf('caching sandbox_available?[%s]', @cache[:sandbox_available?]))
325
- @log.error('sandbox support is not available, please install the "sahara" gem first, https://github.com/jedi4ever/sahara') unless sandbox_available
326
-
327
- return sandbox_available
328
- end
329
-
330
- ##
331
- # sandbox_on
332
- # runs `vagrant sandbox on` from the Vagrantfile path
333
- def sandbox_on
334
- if self.sandbox_available?
335
- return self.vagrant(sprintf('sandbox on %s', @name))
336
- else
337
- raise ExternalError.new('sandbox plugin not installed')
338
- end
339
- end
340
-
341
- ##
342
- # sandbox_off
343
- # runs `vagrant sandbox off` from the Vagrantfile path
344
- def sandbox_off
345
- if self.sandbox_available?
346
- return self.vagrant(sprintf('sandbox off %s', @name))
347
- else
348
- raise ExternalError.new('sandbox plugin not installed')
349
- end
350
- end
351
-
352
- ##
353
- # sandbox_rollback
354
- # runs `vagrant sandbox rollback` from the Vagrantfile path
355
- def sandbox_rollback
356
- if self.sandbox_available?
357
- self.disconnect_ssh_tunnel
358
- self.vagrant(sprintf('sandbox rollback %s', @name))
359
- self.connect_ssh_tunnel
360
- else
361
- raise ExternalError.new('sandbox plugin not installed')
362
- end
363
- end
364
-
365
- ##
366
- # sandbox_commit
367
- # runs `vagrant sandbox commit` from the Vagrantfile path
368
- def sandbox_commit
369
- if self.sandbox_available?
370
- self.disconnect_ssh_tunnel
371
- self.vagrant(sprintf('sandbox commit %s', @name))
372
- self.connect_ssh_tunnel
373
- else
374
- raise ExternalError.new('sandbox plugin not installed')
375
- end
376
- end
377
-
378
312
  ##
379
313
  # get_ssh_info
380
314
  #
@@ -386,7 +320,7 @@ class Rouster
386
320
  h = Hash.new()
387
321
 
388
322
  if @ssh_info.class.eql?(Hash)
389
- @log.debug('using cached SSH info')
323
+ @logger.debug('using cached SSH info')
390
324
  h = @ssh_info
391
325
  else
392
326
 
@@ -404,7 +338,7 @@ class Rouster
404
338
  unless @sshkey.eql?(key)
405
339
  h[:identity_file] = key
406
340
  else
407
- @log.info(sprintf('using specified key[%s] instead of discovered key[%s]', @sshkey, key))
341
+ @logger.info(sprintf('using specified key[%s] instead of discovered key[%s]', @sshkey, key))
408
342
  h[:identity_file] = @sshkey
409
343
  end
410
344
 
@@ -424,14 +358,37 @@ class Rouster
424
358
  #
425
359
  # raises its own InternalError if the machine isn't running, otherwise returns Net::SSH connection object
426
360
  def connect_ssh_tunnel
427
- @log.debug('opening SSH tunnel..')
428
361
 
429
- status = self.status()
430
- if status.eql?('running')
431
- self.get_ssh_info()
432
- @ssh = Net::SSH.start(@ssh_info[:hostname], @ssh_info[:user], :port => @ssh_info[:ssh_port], :keys => [@sshkey], :paranoid => false)
362
+ if self.is_passthrough?
363
+ if self.passthrough[:type].eql?(:local)
364
+ return false
365
+ else
366
+ @logger.debug('opening remote SSH tunnel..')
367
+ @ssh = Net::SSH.start(
368
+ @passthrough[:host],
369
+ @passthrough[:user],
370
+ :port => @passthrough[:port],
371
+ :keys => [ @passthrough[:key] ],
372
+ :paranoid => false
373
+ )
374
+ end
433
375
  else
434
- raise InternalError.new(sprintf('VM is not running[%s], unable open SSH tunnel', status))
376
+ # not a passthrough, normal connection
377
+ status = self.status()
378
+
379
+ if status.eql?('running')
380
+ self.get_ssh_info()
381
+ @logger.debug('opening VM SSH tunnel..')
382
+ @ssh = Net::SSH.start(
383
+ @ssh_info[:hostname],
384
+ @ssh_info[:user],
385
+ :port => @ssh_info[:ssh_port],
386
+ :keys => [@sshkey],
387
+ :paranoid => false
388
+ )
389
+ else
390
+ raise InternalError.new(sprintf('VM is not running[%s], unable open SSH tunnel', status))
391
+ end
435
392
  end
436
393
 
437
394
  @ssh
@@ -443,7 +400,7 @@ class Rouster
443
400
  # shuts down the persistent Net::SSH tunnel
444
401
  #
445
402
  def disconnect_ssh_tunnel
446
- @log.debug('closing SSH tunnel..')
403
+ @logger.debug('closing SSH tunnel..')
447
404
 
448
405
  @ssh.shutdown! unless @ssh.nil?
449
406
  @ssh = nil
@@ -459,13 +416,19 @@ class Rouster
459
416
  return @ostype
460
417
  end
461
418
 
419
+ # TODO switch to file based detection
420
+ # Ubuntu - /etc/os-release
421
+ # Solaris - /etc/release
422
+ # RHEL/CentOS - /etc/redhat-release
423
+ # OSX - ?
424
+
462
425
  res = nil
463
426
  uname = self.run('uname -a')
464
427
 
465
428
  case uname
466
429
  when /Darwin/i
467
430
  res = :osx
468
- when /Sun|Solaris/i
431
+ when /SunOS|Solaris/i
469
432
  res =:solaris
470
433
  when /Ubuntu/i
471
434
  res = :ubuntu
@@ -499,7 +462,7 @@ class Rouster
499
462
  # TODO what happens when we pass a wildcard as remote_file?
500
463
 
501
464
  local_file = local_file.nil? ? File.basename(remote_file) : local_file
502
- @log.debug(sprintf('scp from VM[%s] to host[%s]', remote_file, local_file))
465
+ @logger.debug(sprintf('scp from VM[%s] to host[%s]', remote_file, local_file))
503
466
 
504
467
  begin
505
468
  @ssh.scp.download!(remote_file, local_file)
@@ -520,7 +483,7 @@ class Rouster
520
483
  # * [remote_file] - full or relative path (based on ~vagrant) of filename to upload to
521
484
  def put(local_file, remote_file=nil)
522
485
  remote_file = remote_file.nil? ? File.basename(local_file) : remote_file
523
- @log.debug(sprintf('scp from host[%s] to VM[%s]', local_file, remote_file))
486
+ @logger.debug(sprintf('scp from host[%s] to VM[%s]', local_file, remote_file))
524
487
 
525
488
  raise FileTransferError.new(sprintf('unable to put[%s], local file does not exist', local_file)) unless File.file?(local_file)
526
489
 
@@ -537,7 +500,7 @@ class Rouster
537
500
  #
538
501
  # convenience getter for @passthrough truthiness
539
502
  def is_passthrough?
540
- self.passthrough.eql?(true)
503
+ @passthrough.class.eql?(Hash)
541
504
  end
542
505
 
543
506
  ##
@@ -545,7 +508,7 @@ class Rouster
545
508
  #
546
509
  # convenience getter for @sudo truthiness
547
510
  def uses_sudo?
548
- self.sudo.eql?(true)
511
+ @sudo.eql?(true)
549
512
  end
550
513
 
551
514
  ##
@@ -553,7 +516,7 @@ class Rouster
553
516
  #
554
517
  # destroy and then up the machine in question
555
518
  def rebuild
556
- @log.debug('rebuild()')
519
+ @logger.debug('rebuild()')
557
520
  self.destroy
558
521
  self.up
559
522
  end
@@ -566,10 +529,10 @@ class Rouster
566
529
  # parameters
567
530
  # * [wait] - number of seconds to wait until is_available_via_ssh?() returns true before assuming failure
568
531
  def restart(wait=nil)
569
- @log.debug('restart()')
532
+ @logger.debug('restart()')
570
533
 
571
- if self.is_passthrough? and self.passthrough.eql?(local)
572
- @log.warn(sprintf('intercepted [restart] sent to a local passthrough, no op'))
534
+ if self.is_passthrough? and self.passthrough[:type].eql?(:local)
535
+ @logger.warn(sprintf('intercepted [restart] sent to a local passthrough, no op'))
573
536
  return nil
574
537
  end
575
538
 
@@ -589,7 +552,7 @@ class Rouster
589
552
  if wait
590
553
  inc = wait.to_i / 10
591
554
  0..wait.each do |e|
592
- @log.debug(sprintf('waiting for reboot: round[%s], step[%s], total[%s]', e, inc, wait))
555
+ @logger.debug(sprintf('waiting for reboot: round[%s], step[%s], total[%s]', e, inc, wait))
593
556
  return true if self.is_available_via_ssh?()
594
557
  sleep inc
595
558
  end
@@ -615,38 +578,22 @@ class Rouster
615
578
  cmd = sprintf('%s > %s 2> %s', command, tmp_file, tmp_file) # this is a holdover from Salesforce::Vagrant, can we use '2&>1' here?
616
579
  res = `#{cmd}` # what does this actually hold?
617
580
 
618
- @log.info(sprintf('host running: [%s]', cmd))
581
+ @logger.info(sprintf('host running: [%s]', cmd))
619
582
 
620
583
  output = File.read(tmp_file)
621
584
  File.delete(tmp_file) or raise InternalError.new(sprintf('unable to delete [%s]: %s', tmp_file, $!))
622
585
 
586
+ self.output.push(output)
587
+ @logger.debug(sprintf('output: [%s]', output))
588
+
623
589
  unless $?.success?
624
590
  raise LocalExecutionError.new(sprintf('command [%s] exited with code [%s], output [%s]', cmd, $?.to_i(), output))
625
591
  end
626
592
 
627
- self.output.push(output)
628
- @log.debug(sprintf('output: [%s]', output))
629
-
630
593
  @exitcode = $?.to_i()
631
594
  output
632
595
  end
633
596
 
634
- ##
635
- # vagrant
636
- #
637
- # abstraction layer to call vagrant faces
638
- #
639
- # parameters
640
- # * <face> - vagrant face to call (include arguments)
641
- def vagrant(face)
642
- if self.is_passthrough?
643
- @log.info(sprintf('calling [vagrant %s] on a passthrough host is a noop', face))
644
- return nil
645
- end
646
-
647
- self._run(sprintf('cd %s; vagrant %s', File.dirname(@vagrantfile), face))
648
- end
649
-
650
597
  ##
651
598
  # get_output
652
599
  #
@@ -686,7 +633,7 @@ class Rouster
686
633
  def traverse_up(startdir=Dir.pwd, filename=nil, levels=10)
687
634
  raise InternalError.new('must specify a filename') if filename.nil?
688
635
 
689
- @log.debug(sprintf('traverse_up() looking for [%s] in [%s], up to [%s] levels', filename, startdir, levels)) unless @log.nil?
636
+ @logger.debug(sprintf('traverse_up() looking for [%s] in [%s], up to [%s] levels', filename, startdir, levels)) unless @logger.nil?
690
637
 
691
638
  dirs = startdir.split('/')
692
639
  count = 0
@@ -715,6 +662,11 @@ class Rouster
715
662
  def check_key_permissions(key, fix=false)
716
663
  allowed_modes = ['0400', '0600']
717
664
 
665
+ if key.match(/\.pub$/)
666
+ # if this is the public half of the key, be more permissive
667
+ allowed_modes << '0644'
668
+ end
669
+
718
670
  raw = self._run(sprintf('ls -l %s', key))
719
671
  perms = self.parse_ls_string(raw)
720
672