rouster 0.53 → 0.57

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