taste_tester 0.0.13 → 0.0.14

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.
@@ -19,6 +19,7 @@ require 'taste_tester/host'
19
19
  require 'taste_tester/config'
20
20
  require 'taste_tester/client'
21
21
  require 'taste_tester/logging'
22
+ require 'taste_tester/exceptions'
22
23
 
23
24
  module TasteTester
24
25
  # Functionality dispatch
@@ -28,6 +29,7 @@ module TasteTester
28
29
  def self.start
29
30
  server = TasteTester::Server.new
30
31
  return if TasteTester::Server.running?
32
+
31
33
  server.start
32
34
  end
33
35
 
@@ -45,7 +47,12 @@ module TasteTester
45
47
  server = TasteTester::Server.new
46
48
  if TasteTester::Server.running?
47
49
  logger.warn("Local taste-tester server running on port #{server.port}")
48
- if server.latest_uploaded_ref
50
+ if TasteTester::Config.no_repo && server.last_upload_time
51
+ logger.warn("Last upload time was #{server.last_upload_time}")
52
+ elsif !TasteTester::Config.no_repo && server.latest_uploaded_ref
53
+ if server.last_upload_time
54
+ logger.warn("Last upload time was #{server.last_upload_time}")
55
+ end
49
56
  logger.warn('Latest uploaded revision is ' +
50
57
  server.latest_uploaded_ref)
51
58
  else
@@ -77,12 +84,16 @@ module TasteTester
77
84
  end
78
85
  server = TasteTester::Server.new
79
86
  unless TasteTester::Config.linkonly
80
- repo = BetweenMeals::Repo.get(
81
- TasteTester::Config.repo_type,
82
- TasteTester::Config.repo,
83
- logger,
84
- )
85
- unless repo.exists?
87
+ if TasteTester::Config.no_repo
88
+ repo = nil
89
+ else
90
+ repo = BetweenMeals::Repo.get(
91
+ TasteTester::Config.repo_type,
92
+ TasteTester::Config.repo,
93
+ logger,
94
+ )
95
+ end
96
+ if repo && !repo.exists?
86
97
  fail "Could not open repo from #{TasteTester::Config.repo}"
87
98
  end
88
99
  end
@@ -93,12 +104,11 @@ module TasteTester
93
104
  tested_hosts = []
94
105
  hosts.each do |hostname|
95
106
  host = TasteTester::Host.new(hostname, server)
96
- if host.in_test?
97
- username = host.who_is_testing
98
- logger.error("User #{username} is already testing on #{hostname}")
99
- else
107
+ begin
100
108
  host.test
101
109
  tested_hosts << hostname
110
+ rescue TasteTester::Exceptions::AlreadyTestingError => e
111
+ logger.error("User #{e.username} is already testing on #{hostname}")
102
112
  end
103
113
  end
104
114
  unless TasteTester::Config.skip_post_test_hook ||
@@ -106,6 +116,24 @@ module TasteTester
106
116
  TasteTester::Hooks.post_test(TasteTester::Config.dryrun, repo,
107
117
  tested_hosts)
108
118
  end
119
+ # Strictly: hosts and tested_hosts should be sets to eliminate variance in
120
+ # order or duplicates. The exact comparison works here because we're
121
+ # building tested_hosts from hosts directly.
122
+ if tested_hosts == hosts
123
+ # No exceptions, complete success: every host listed is now configured
124
+ # to use our chef-zero instance.
125
+ exit(0)
126
+ end
127
+ if tested_hosts.empty?
128
+ # All requested hosts are being tested by another user. We didn't change
129
+ # their configuration.
130
+ exit(3)
131
+ end
132
+ # Otherwise, we got a mix of success and failure due to being tested by
133
+ # another user. We'll be pessemistic and return an error because the
134
+ # intent to taste test the complete list was not successful.
135
+ # code.
136
+ exit(2)
109
137
  end
110
138
 
111
139
  def self.untest
@@ -149,7 +177,7 @@ module TasteTester
149
177
 
150
178
  def self.upload
151
179
  server = TasteTester::Server.new
152
- # On a fore-upload rather than try to clean up whatever's on the server
180
+ # On a force-upload rather than try to clean up whatever's on the server
153
181
  # we'll restart chef-zero which will clear everything and do a full
154
182
  # upload
155
183
  if TasteTester::Config.force_upload
@@ -182,5 +210,165 @@ module TasteTester
182
210
  logger.error(exception.backtrace.join("\n"))
183
211
  exit 1
184
212
  end
213
+
214
+ def self.impact
215
+ # Use the repository specified in config.rb to calculate the changes
216
+ # that may affect Chef. These changes will be further analyzed to
217
+ # determine specific roles which may change due to modifed dependencies.
218
+ repo = BetweenMeals::Repo.get(
219
+ TasteTester::Config.repo_type,
220
+ TasteTester::Config.repo,
221
+ logger,
222
+ )
223
+ if repo && !repo.exists?
224
+ fail "Could not open repo from #{TasteTester::Config.repo}"
225
+ end
226
+
227
+ changes = _find_changeset(repo)
228
+
229
+ # Use Knife (or custom logic) to check the dependencies of each role
230
+ # against the list of changes. `impacted_roles` will contian the set
231
+ # of roles with direct or indirect (dependency) modifications.
232
+ impacted_roles = TasteTester::Hooks.impact_find_roles(changes)
233
+ impacted_roles ||= _find_roles(changes)
234
+
235
+ # Do any post processing required on the list of impacted roles, such
236
+ # as looking up hostnames associated with each role.
237
+ final_impact = TasteTester::Hooks.post_impact(impacted_roles)
238
+ final_impact ||= impacted_roles
239
+
240
+ # Print the calculated impact. If a print hook is defined that
241
+ # returns true, then the default print function is skipped.
242
+ unless TasteTester::Hooks.print_impact(final_impact)
243
+ _print_impact(final_impact)
244
+ end
245
+ end
246
+
247
+ def self._find_changeset(repo)
248
+ # We want to compare changes in the current directory (working set) with
249
+ # the "most recent" commit in the VCS. For SVN, this will be the latest
250
+ # commit on the checked out repository (i.e. 'trunk'). Git/Hg may have
251
+ # different tags or labels assigned to the master branch, (i.e. 'master',
252
+ # 'stable', etc.) and should be configured if different than the default.
253
+ start_ref = case repo
254
+ when BetweenMeals::Repo::Svn
255
+ repo.latest_revision
256
+ when BetweenMeals::Repo::Git
257
+ TasteTester::Config.vcs_start_ref_git
258
+ when BetweenMeals::Repo::Hg
259
+ TasteTester::Config.vcs_start_ref_hg
260
+ end
261
+ end_ref = TasteTester::Config.vcs_end_ref
262
+
263
+ changeset = BetweenMeals::Changeset.new(
264
+ logger,
265
+ repo,
266
+ start_ref,
267
+ end_ref,
268
+ {
269
+ :cookbook_dirs =>
270
+ TasteTester::Config.relative_cookbook_dirs,
271
+ :role_dir =>
272
+ TasteTester::Config.relative_role_dir,
273
+ :databag_dir =>
274
+ TasteTester::Config.relative_databag_dir,
275
+ },
276
+ @track_symlinks,
277
+ )
278
+
279
+ return changeset
280
+ end
281
+
282
+ def self._find_roles(changes)
283
+ if TasteTester::Config.relative_cookbook_dirs.length > 1
284
+ logger.error('Knife deps does not support multiple cookbook paths.')
285
+ logger.error('Please flatten the cookbooks into a single directory' +
286
+ ' or override the impact_find_roles function.')
287
+ exit(1)
288
+ end
289
+
290
+ cookbooks = Set.new(changes.cookbooks)
291
+ roles = Set.new(changes.roles)
292
+ databags = Set.new(changes.databags)
293
+
294
+ if cookbooks.empty? && roles.empty?
295
+ logger.warn('No cookbooks or roles have been modified.')
296
+ return Set.new
297
+ end
298
+
299
+ unless cookbooks.empty?
300
+ logger.info('Modified Cookbooks:')
301
+ cookbooks.each { |cb| logger.info("\t#{cb}") }
302
+ end
303
+ unless roles.empty?
304
+ logger.info('Modified Roles:')
305
+ roles.each { |r| logger.info("\t#{r}") }
306
+ end
307
+ unless databags.empty?
308
+ logger.info('Modified Databags:')
309
+ databags.each { |db| logger.info("\t#{db}") }
310
+ end
311
+
312
+ # Use Knife to list the dependecies for each role in the roles directory.
313
+ # This creates a recursive tree structure that is then searched for
314
+ # instances of modified cookbooks. This can be slow since it must read
315
+ # every line of the Knife output, then search all roles for dependencies.
316
+ # If you have a custom way to calculate these reverse dependencies, this
317
+ # is the part you would replace.
318
+ logger.info('Finding dependencies (this may take a minute or two)...')
319
+ knife = Mixlib::ShellOut.new(
320
+ "knife deps /#{TasteTester::Config.role_dir}/*.rb" +
321
+ " --config #{TasteTester::Config.knife_config}" +
322
+ " --chef-repo-path #{TasteTester::Config.absolute_base_dir}" +
323
+ ' --tree --recurse',
324
+ )
325
+ knife.run_command
326
+ knife.error!
327
+
328
+ # Collapse the output from Knife into a hash structure that maps roles
329
+ # to the set of their dependencies. This will ignore duplicates in the
330
+ # Knife output, but must still process each line.
331
+ logger.info('Processing Dependencies...')
332
+ deps_hash = {}
333
+ curr_role = nil
334
+
335
+ knife.stdout.each_line do |line|
336
+ elem = line.rstrip
337
+ if elem.length == elem.lstrip.length
338
+ curr_role = elem
339
+ deps_hash[curr_role] = Set.new
340
+ else
341
+ deps_hash[curr_role].add(File.basename(elem, File.extname(elem)))
342
+ end
343
+ end
344
+
345
+ # Now we can search for modified dependencies by iterating over each
346
+ # role and checking the hash created earlier. Roles that have been
347
+ # modified directly are automatically included in the impacted set.
348
+ impacted_roles = Set.new(roles.map(&:name))
349
+ deps_hash.each do |role, deplist|
350
+ cookbooks.each do |cb|
351
+ if deplist.include?(cb.name)
352
+ impacted_roles.add(role)
353
+ logger.info("\tFound dependency: #{role} --> #{cb.name}")
354
+ break
355
+ end
356
+ end
357
+ end
358
+
359
+ return impacted_roles
360
+ end
361
+
362
+ def self._print_impact(final_impact)
363
+ if TasteTester::Config.json
364
+ puts JSON.pretty_generate(final_impact.to_a)
365
+ elsif final_impact.empty?
366
+ logger.warn('No impacted roles were found.')
367
+ else
368
+ logger.warn('The following roles have modified dependencies.' +
369
+ ' Please test a host in each of these roles.')
370
+ final_impact.each { |r| logger.warn("\t#{r}") }
371
+ end
372
+ end
185
373
  end
186
374
  end
@@ -37,6 +37,7 @@ module TasteTester
37
37
  config_file '/etc/taste-tester-config.rb'
38
38
  plugin_path nil
39
39
  chef_zero_path nil
40
+ bundle false
40
41
  verbosity Logger::WARN
41
42
  timestamp false
42
43
  user 'root'
@@ -51,6 +52,7 @@ module TasteTester
51
52
  timestamp_file '/etc/chef/test_timestamp'
52
53
  use_ssh_tunnels false
53
54
  ssh_command 'ssh'
55
+ ssh_connect_timeout 5
54
56
  use_ssl true
55
57
  chef_zero_logging true
56
58
  chef_config_path '/etc/chef'
@@ -58,6 +60,16 @@ module TasteTester
58
60
  my_hostname nil
59
61
  track_symlinks false
60
62
  transport 'ssh'
63
+ no_repo false
64
+ json false
65
+
66
+ # Start/End refs for calculating changes in the repo.
67
+ # - start_ref should be the "master" commit of the repository
68
+ # - end_ref should be nil to compare with the working set,
69
+ # or something like '.' to compare with the most recent commit
70
+ vcs_start_ref_git 'origin/HEAD'
71
+ vcs_start_ref_hg 'master'
72
+ vcs_end_ref nil
61
73
 
62
74
  skip_pre_upload_hook false
63
75
  skip_post_upload_hook false
@@ -97,6 +109,10 @@ module TasteTester
97
109
  end
98
110
  end
99
111
 
112
+ def self.absolute_base_dir
113
+ File.join(repo, base_dir)
114
+ end
115
+
100
116
  def self.chef_port
101
117
  require 'taste_tester/state'
102
118
  port_range = (
@@ -116,11 +132,8 @@ module TasteTester
116
132
  end
117
133
 
118
134
  def self.testing_end_time
119
- if TasteTester::Config.testing_until
120
- TasteTester::Config.testing_until
121
- else
135
+ TasteTester::Config.testing_until ||
122
136
  Time.now + TasteTester::Config.testing_time
123
- end
124
137
  end
125
138
  end
126
139
  end
@@ -22,5 +22,12 @@ module TasteTester
22
22
  end
23
23
  class NoOpError < StandardError
24
24
  end
25
+ class AlreadyTestingError < StandardError
26
+ attr_reader :username
27
+
28
+ def initialize(username)
29
+ @username = username
30
+ end
31
+ end
25
32
  end
26
33
  end
@@ -48,6 +48,21 @@ module TasteTester
48
48
  # Additional checks you want to do on the repo
49
49
  def self.repo_checks(_dryrun, _repo); end
50
50
 
51
+ # Find the set of roles dependent on the changed files.
52
+ # If returning something other than a set of roles, post_impact and/or
53
+ # print_impact should be specified to handle the output.
54
+ def self.impact_find_roles(_changes); end
55
+
56
+ # Do stuff after we find impacted roles
57
+ # This should return a Set object with the final impact. To return more
58
+ # complex data, you must also provide a print_impact function which returns
59
+ # true to override the default output.
60
+ def self.post_impact(_impacted_roles); end
61
+
62
+ # Customized the printed output of impact
63
+ # If this method returns true, the default output will not be printed.
64
+ def self.print_impact(_final_impact); end
65
+
51
66
  def self.get(file)
52
67
  path = File.expand_path(file)
53
68
  logger.warn("Loading plugin at #{path}")
@@ -23,12 +23,16 @@ require 'taste_tester/ssh'
23
23
  require 'taste_tester/noop'
24
24
  require 'taste_tester/locallink'
25
25
  require 'taste_tester/tunnel'
26
+ require 'taste_tester/exceptions'
26
27
 
27
28
  module TasteTester
28
29
  # Manage state of the remote node
29
30
  class Host
30
31
  include TasteTester::Logging
31
32
 
33
+ TASTE_TESTER_CONFIG = 'client-taste-tester.rb'.freeze
34
+ USER_PREAMBLE = '# TasteTester by '.freeze
35
+
32
36
  attr_reader :name
33
37
 
34
38
  def initialize(name, server)
@@ -94,22 +98,45 @@ module TasteTester
94
98
  @tunnel.run
95
99
  end
96
100
 
97
- @serialized_config = Base64.encode64(config).delete("\n")
101
+ serialized_config = Base64.encode64(config).delete("\n")
98
102
 
99
103
  # Then setup the testing
100
104
  transport = get_transport
101
105
 
106
+ # see if someone else is taste-testing
107
+ transport << we_testing
108
+
102
109
  transport << 'logger -t taste-tester Moving server into taste-tester' +
103
110
  " for #{@user}"
104
111
  transport << touchcmd
105
- transport << "/bin/echo -n '#{@serialized_config}' | base64 --decode" +
106
- " > #{TasteTester::Config.chef_config_path}/client-taste-tester.rb"
107
- transport << "rm -vf #{TasteTester::Config.chef_config_path}/" +
108
- TasteTester::Config.chef_config
109
- transport << "( ln -vs #{TasteTester::Config.chef_config_path}" +
110
- "/client-taste-tester.rb #{TasteTester::Config.chef_config_path}/" +
112
+ # shell redirection is also racy, so make a temporary file first
113
+ transport << "tmpconf=$(mktemp #{TasteTester::Config.chef_config_path}/" +
114
+ "#{TASTE_TESTER_CONFIG}.TMPXXXXXX)"
115
+ transport << "/bin/echo -n \"#{serialized_config}\" | base64 --decode" +
116
+ ' > "${tmpconf}"'
117
+ # then rename it to replace any existing file
118
+ transport << 'mv -f "${tmpconf}" ' +
119
+ "#{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}"
120
+ transport << "( ln -vsf #{TasteTester::Config.chef_config_path}" +
121
+ "/#{TASTE_TESTER_CONFIG} #{TasteTester::Config.chef_config_path}/" +
111
122
  "#{TasteTester::Config.chef_config}; true )"
112
- transport.run!
123
+
124
+ # look again to see if someone else is taste-testing. This is where
125
+ # we work out if we won or lost a race with another user.
126
+ transport << we_testing
127
+
128
+ transport.run
129
+
130
+ case transport.status
131
+ when 0
132
+ # no problem, keep going.
133
+ nil
134
+ when 42
135
+ fail TasteTester::Exceptions::AlreadyTestingError,
136
+ transport.output.chomp
137
+ else
138
+ transport.error!
139
+ end
113
140
 
114
141
  # Then run any other stuff they wanted
115
142
  cmds = TasteTester::Hooks.test_remote_cmds(
@@ -117,7 +144,7 @@ module TasteTester
117
144
  @name,
118
145
  )
119
146
 
120
- if cmds && cmds.any?
147
+ if cmds&.any?
121
148
  transport = get_transport
122
149
  cmds.each { |c| transport << c }
123
150
  transport.run!
@@ -132,15 +159,12 @@ module TasteTester
132
159
  end
133
160
  config_prod = TasteTester::Config.chef_config.split('.').join('-prod.')
134
161
  [
135
- "rm -vf #{TasteTester::Config.chef_config_path}/" +
136
- TasteTester::Config.chef_config,
137
- "rm -vf #{TasteTester::Config.chef_config_path}/client-taste-tester.rb",
138
- "ln -vs #{TasteTester::Config.chef_config_path}/#{config_prod} " +
162
+ "ln -vsf #{TasteTester::Config.chef_config_path}/#{config_prod} " +
139
163
  "#{TasteTester::Config.chef_config_path}/" +
140
164
  TasteTester::Config.chef_config,
141
- "rm -vf #{TasteTester::Config.chef_config_path}/client.pem",
142
- "ln -vs #{TasteTester::Config.chef_config_path}/client-prod.pem " +
165
+ "ln -vsf #{TasteTester::Config.chef_config_path}/client-prod.pem " +
143
166
  "#{TasteTester::Config.chef_config_path}/client.pem",
167
+ "rm -vf #{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}",
144
168
  "rm -vf #{TasteTester::Config.timestamp_file}",
145
169
  'logger -t taste-tester Returning server to production',
146
170
  ].each do |cmd|
@@ -149,43 +173,25 @@ module TasteTester
149
173
  transport.run!
150
174
  end
151
175
 
152
- def who_is_testing
153
- transport = get_transport
154
- transport << 'grep "^# TasteTester by"' +
155
- " #{TasteTester::Config.chef_config_path}/" +
156
- TasteTester::Config.chef_config
157
- output = transport.run
158
- if output.first.zero?
159
- user = output.last.match(/# TasteTester by (.*)$/)
160
- if user
161
- return user[1]
162
- end
163
- end
164
-
165
- # Legacy FB stuff, remove after migration. Safe for everyone else.
166
- transport = get_transport
167
- transport << "file #{TasteTester::Config.chef_config_path}/" +
176
+ def we_testing
177
+ config_file = "#{TasteTester::Config.chef_config_path}/" +
168
178
  TasteTester::Config.chef_config
169
- output = transport.run
170
- if output.first.zero?
171
- user = output.last.match(/client-(.*)-(taste-tester|test).rb/)
172
- if user
173
- return user[1]
174
- end
175
- end
176
-
177
- return nil
178
- end
179
-
180
- def in_test?
181
- transport = get_transport
182
- transport << "test -f #{TasteTester::Config.timestamp_file}"
183
- if transport.run.first.zero? && who_is_testing &&
184
- who_is_testing != ENV['USER']
185
- true
186
- else
187
- false
188
- end
179
+ # Look for signature of TasteTester
180
+ # 1. Look for USER_PREAMBLE line prefix
181
+ # 2. See if user is us, or someone else
182
+ # 3. if someone else is testing: emit username, exit with code 42 which
183
+ # short circuits the test verb
184
+ # This is written as a squiggly heredoc so the indentation of the awk is
185
+ # preserved. Later we remove the newlines to make it a bit easier to read.
186
+ shellcode = <<~ENDOFSHELLCODE
187
+ awk "\\$0 ~ /^#{USER_PREAMBLE}/{
188
+ if (\\$NF != \\"#{@user}\\"){
189
+ print \\$NF;
190
+ exit 42
191
+ }
192
+ }" #{config_file}
193
+ ENDOFSHELLCODE
194
+ shellcode.delete("\n")
189
195
  end
190
196
 
191
197
  def keeptesting
@@ -220,40 +226,90 @@ module TasteTester
220
226
  if TasteTester::Config.use_ssh_tunnels
221
227
  url = "#{scheme}://localhost:#{@tunnel.port}"
222
228
  else
223
- url = "#{scheme}://#{@server.host}"
229
+ url = +"#{scheme}://#{@server.host}"
224
230
  url << ":#{TasteTester::State.port}" if TasteTester::State.port
225
231
  end
226
- # rubocop:disable Metrics/LineLength
227
- ttconfig = <<-EOS
228
- # TasteTester by #{@user}
229
- # Prevent people from screwing up their permissions
230
- if Process.euid != 0
231
- puts 'Please run chef as root!'
232
- Process.exit!
233
- end
232
+ ttconfig = <<~ENDOFSCRIPT
233
+ #{USER_PREAMBLE}#{@user}
234
+ # Prevent people from screwing up their permissions
235
+ if Process.euid != 0
236
+ puts 'Please run chef as root!'
237
+ Process.exit!
238
+ end
234
239
 
235
- log_level :info
236
- log_location STDOUT
237
- chef_server_url '#{url}'
238
- ssl_verify_mode :verify_none
239
- ohai.plugin_path << '#{TasteTester::Config.chef_config_path}/ohai_plugins'
240
+ log_level :info
241
+ log_location STDOUT
242
+ ssl_verify_mode :verify_none
243
+ ohai.plugin_path << File.join('#{TasteTester::Config.chef_config_path}', 'ohai_plugins')
244
+ ENDOFSCRIPT
240
245
 
241
- EOS
242
- # rubocop:enable Metrics/LineLength
246
+ if TasteTester::Config.bundle
247
+ ttconfig += <<~ENDOFSCRIPT
248
+ taste_tester_dest = File.join(Dir.tmpdir, 'taste-tester')
249
+ puts 'INFO: Downloading bundle from #{url}...'
250
+ FileUtils.rmtree(taste_tester_dest)
251
+ FileUtils.mkpath(taste_tester_dest)
252
+ FileUtils.touch(File.join(taste_tester_dest, 'chefignore'))
253
+ uri = URI('#{url}/file_store/tt.tgz')
254
+ Net::HTTP.start(
255
+ uri.host,
256
+ uri.port,
257
+ :use_ssl => #{TasteTester::Config.use_ssl},
258
+ # we expect self signed certificates
259
+ :verify_mode => OpenSSL::SSL::VERIFY_NONE,
260
+ ) do |http|
261
+ http.request_get(uri) do |response|
262
+ # the use of stringIO means we are buffering the entire file in
263
+ # memory. This isn't very efficient, but it should work for
264
+ # most practical cases.
265
+ stream = Zlib::GzipReader.new(StringIO.new(response.body))
266
+ Gem::Package::TarReader.new(stream).each do |e|
267
+ dest = File.join(taste_tester_dest, e.full_name)
268
+ FileUtils.mkpath(File.dirname(dest))
269
+ if e.symlink?
270
+ File.symlink(e.header.linkname, dest)
271
+ else
272
+ File.open(dest, 'wb+') do |f|
273
+ # https://github.com/rubygems/rubygems/pull/2303
274
+ # IO.copy_stream(e, f)
275
+ # workaround:
276
+ f.write(e.read)
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
282
+ puts 'INFO: Download complete'
283
+ solo true
284
+ local_mode true
285
+ ENDOFSCRIPT
286
+ else
287
+ ttconfig += <<~ENDOFSCRIPT
288
+ chef_server_url '#{url}'
289
+ ENDOFSCRIPT
290
+ end
243
291
 
244
292
  extra = TasteTester::Hooks.test_remote_client_rb_extra_code(@name)
245
293
  if extra
246
- ttconfig += <<-EOS
247
- # Begin user-hook specified code
248
- #{extra}
249
- # End user-hook secified code
294
+ ttconfig += <<~ENDOFSCRIPT
295
+ # Begin user-hook specified code
296
+ #{extra}
297
+ # End user-hook secified code
250
298
 
251
- EOS
299
+ ENDOFSCRIPT
252
300
  end
253
301
 
254
- ttconfig += <<-EOS
255
- puts 'INFO: Running on #{@name} in taste-tester by #{@user}'
256
- EOS
302
+ ttconfig += <<~ENDOFSCRIPT
303
+ puts 'INFO: Running on #{@name} in taste-tester by #{@user}'
304
+ ENDOFSCRIPT
305
+
306
+ if TasteTester::Config.bundle
307
+ # This is last in the configuration file because it needs to override
308
+ # any values in test_remote_client_rb_extra_code
309
+ ttconfig += <<~ENDOFSCRIPT
310
+ chef_repo_path taste_tester_dest
311
+ ENDOFSCRIPT
312
+ end
257
313
  return ttconfig
258
314
  end
259
315
  end
@@ -22,7 +22,7 @@ module TasteTester
22
22
  include TasteTester::Logging
23
23
  include BetweenMeals::Util
24
24
 
25
- attr_reader :output
25
+ attr_reader :output, :status
26
26
 
27
27
  def initialize
28
28
  @host = 'localhost'
@@ -43,7 +43,11 @@ module TasteTester
43
43
  @status, @output = exec!(cmd, logger)
44
44
  rescue StandardError => e
45
45
  logger.error(e.message)
46
- raise TasteTester::Exceptions::LocalLinkError
46
+ error!
47
+ end
48
+
49
+ def error!
50
+ fail TasteTester::Exceptions::LocalLinkError
47
51
  end
48
52
 
49
53
  private
@@ -35,8 +35,8 @@ module TasteTester
35
35
  @logger ||= Logger.new(STDOUT)
36
36
  end
37
37
 
38
- def self.formatterproc=(p)
39
- @@formatter_proc = p
38
+ def self.formatterproc=(process)
39
+ @@formatter_proc = process
40
40
  end
41
41
 
42
42
  def self.use_log_formatter=(use_log_formatter)
@@ -49,6 +49,7 @@ module TasteTester
49
49
 
50
50
  def formatter
51
51
  return @@formatter_proc if @@formatter_proc
52
+
52
53
  if @@use_log_formatter
53
54
  proc do |severity, datetime, _progname, msg|
54
55
  if severity == 'ERROR'
@@ -58,7 +59,7 @@ module TasteTester
58
59
  end
59
60
  else
60
61
  proc do |severity, _datetime, _progname, msg|
61
- msg.to_s.prepend("#{severity}: ") unless severity == 'WARN'
62
+ msg.dup.to_s.prepend("#{severity}: ") unless severity == 'WARN'
62
63
  if severity == 'ERROR'
63
64
  msg = msg.to_s.red
64
65
  end