taste_tester 0.0.14 → 0.0.19
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 +4 -4
- data/README.md +33 -8
- data/bin/taste-tester +31 -9
- data/lib/taste_tester/client.rb +49 -40
- data/lib/taste_tester/commands.rb +14 -11
- data/lib/taste_tester/config.rb +2 -0
- data/lib/taste_tester/hooks.rb +7 -7
- data/lib/taste_tester/host.rb +181 -66
- data/lib/taste_tester/locallink.rb +2 -4
- data/lib/taste_tester/noop.rb +1 -3
- data/lib/taste_tester/server.rb +5 -5
- data/lib/taste_tester/ssh.rb +7 -30
- data/lib/taste_tester/ssh_util.rb +127 -0
- data/lib/taste_tester/state.rb +5 -2
- data/lib/taste_tester/tunnel.rb +172 -35
- data/scripts/taste-untester +8 -8
- data/scripts/taste-untester.ps1 +85 -0
- metadata +21 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2fa17de1672814a680aa44b592bc649f3b3ec80b057a0199aed9b3ce13e1fe5
|
4
|
+
data.tar.gz: e9150bc89798e17651102239224763468ca8a65affb7706c4080a409577bf108
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12bf2e2e631987187b455b294bae9db28bd1f50c7aba35bbe9d04b3cbceabec3f65fd665f5ef5c70424bb296438742ebd0a918a5545480ec058ff760ac6f81fa
|
7
|
+
data.tar.gz: 4b6781fe213f7e9447077d6f629c3facc4f2fa596212b8a8f1f8cde40e1f1aba4c66542f1b939b587c87d4e05878320e410e2bd8b9bbec98a40ff97ac52c4806
|
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# Taste Tester
|
2
2
|
|
3
|
-
|
4
|
-
[](https://circleci.com/gh/facebook/taste-tester)
|
3
|
+

|
5
4
|
|
6
5
|
## Intro
|
7
6
|
Ohai!
|
@@ -25,6 +24,7 @@ Typical usage is:
|
|
25
24
|
|
26
25
|
```text
|
27
26
|
vi cookbooks/... # Make your changes and commit locally
|
27
|
+
taste-tester impact # Check where your changes are used
|
28
28
|
taste-tester test -s [host] # Put host in Taste Tester mode
|
29
29
|
ssh root@[host] # Log in to host
|
30
30
|
# Run chef and watch it break
|
@@ -62,6 +62,7 @@ you want to test on, i.e. SSH public/private keys, SSH certificates, Kerberos
|
|
62
62
|
* Colorize
|
63
63
|
* BetweenMeals
|
64
64
|
* Minitar
|
65
|
+
* Chef
|
65
66
|
|
66
67
|
## Automatic Untesting
|
67
68
|
|
@@ -91,7 +92,8 @@ All command-line options are available in the config file:
|
|
91
92
|
* plugin_path (string, no default)
|
92
93
|
* repo (string, default: `#{ENV['HOME']}/ops`)
|
93
94
|
* testing_time (int, default: `3600`)
|
94
|
-
* chef_client_command (
|
95
|
+
* chef_client_command (string, default: `chef-client`)
|
96
|
+
* json (bool, default: `false`)
|
95
97
|
* skip_repo_checks (bool, default: `false`)
|
96
98
|
* skip_pre_upload_hook (bool, default: `false`)
|
97
99
|
* skip_post_upload_hook (bool, default: `false`)
|
@@ -103,7 +105,7 @@ The following options are also available:
|
|
103
105
|
* base_dir - The directory in the repo under which to find chef configs.
|
104
106
|
Default: `chef`
|
105
107
|
* cookbook_dirs - An array of cookbook directories relative to base_dir.
|
106
|
-
Default: `['cookbooks']
|
108
|
+
Default: `['cookbooks']`
|
107
109
|
* role_dir - A directory of roles, relative to base_dir. Default: `roles`
|
108
110
|
* databag_dir - A directory of databags, relative to base_dir.
|
109
111
|
Default: `databags`
|
@@ -112,9 +114,13 @@ The following options are also available:
|
|
112
114
|
* checksum_dir - The checksum directory to put in knife.conf for users. Default:
|
113
115
|
`#{ENV['HOME']}/.chef/checksums`
|
114
116
|
* bundle - use a single tar.gz file for transporting cookbooks, roles and
|
115
|
-
databags to clients. Experimental.
|
117
|
+
databags to clients. Experimental. Value is tri-state:
|
118
|
+
* `false` - server uses knife upload, client uses `chef_server`
|
119
|
+
* `:compatible` - make server support both methods, client uses tar.gz
|
120
|
+
* `true` - server only creates tar.gz, client uses tar.gz
|
121
|
+
Default: false
|
122
|
+
* impact - analyze local changes to determine which hosts/roles to test.
|
116
123
|
Default: false
|
117
|
-
|
118
124
|
|
119
125
|
## Plugin
|
120
126
|
|
@@ -156,10 +162,28 @@ Stuff to do after putting all hosts in test mode.
|
|
156
162
|
|
157
163
|
Additional checks you want to do on the repo as sanity checks.
|
158
164
|
|
159
|
-
|
165
|
+
* self.find_impact(changes)
|
166
|
+
|
167
|
+
Custom implementation of impact analysis. Uses `knife deps` by default. May
|
168
|
+
return any data structure, provided one or both of `self.post_impact` or
|
169
|
+
`self.print_impact` are defined.
|
170
|
+
|
171
|
+
* self.post_impact(basic_impact)
|
172
|
+
|
173
|
+
Stuff to do after preliminary impact analysis. May be used to extend the
|
174
|
+
information generated by `self.find_impact`, reformat the data structure, etc.
|
175
|
+
|
176
|
+
* self.print_impact(final_impact)
|
177
|
+
|
178
|
+
Custom output of calculated impact, useful if defining either of the other
|
179
|
+
impact hooks. Must return a truthy value to prevent the default output from
|
180
|
+
printing.
|
181
|
+
|
182
|
+
## Plugin example
|
160
183
|
|
161
184
|
This is an example `/etc/taste-tester-plugin.rb` to add a user-defined string
|
162
185
|
to `client-taste-tester.rb` on the remote system:
|
186
|
+
|
163
187
|
```
|
164
188
|
Hooks.class_eval do
|
165
189
|
def self.test_remote_client_rb_extra_code(_hostname)
|
@@ -170,7 +194,8 @@ Hooks.class_eval do
|
|
170
194
|
end
|
171
195
|
end
|
172
196
|
```
|
173
|
-
|
197
|
+
|
198
|
+
Be sure to pass this plugin file with `-p` on the command line or set it as
|
174
199
|
`plugin_path` in your `taste-tester-config.rb` file.
|
175
200
|
|
176
201
|
## License
|
data/bin/taste-tester
CHANGED
@@ -41,7 +41,7 @@ module TasteTester
|
|
41
41
|
|
42
42
|
# Do an initial read of the config file if it's in the default place, so
|
43
43
|
# that if people override chef_client_command the help message is correct.
|
44
|
-
if File.
|
44
|
+
if File.exist?(File.expand_path(TasteTester::Config.config_file))
|
45
45
|
TasteTester::Config.from_file(
|
46
46
|
File.expand_path(TasteTester::Config.config_file),
|
47
47
|
)
|
@@ -66,7 +66,8 @@ TLDR; Most common usage is:
|
|
66
66
|
taste-tester untest -s [host] # Put host back in production
|
67
67
|
# (optional - will revert itself after 1 hour)
|
68
68
|
|
69
|
-
And you're done!
|
69
|
+
And you're done!
|
70
|
+
Note: There may be site specific testing instructions, see local documentation for details.
|
70
71
|
|
71
72
|
MODES:
|
72
73
|
test
|
@@ -135,7 +136,6 @@ MODES:
|
|
135
136
|
end
|
136
137
|
|
137
138
|
options = { :config_file => TasteTester::Config.config_file }
|
138
|
-
# rubocop:disable Metrics/BlockLength
|
139
139
|
parser = OptionParser.new do |opts|
|
140
140
|
opts.banner = description
|
141
141
|
|
@@ -143,7 +143,7 @@ MODES:
|
|
143
143
|
opts.separator 'Global options:'.upcase
|
144
144
|
|
145
145
|
opts.on('-c', '--config FILE', 'Config file') do |file|
|
146
|
-
unless File.
|
146
|
+
unless File.exist?(File.expand_path(file))
|
147
147
|
logger.error("Sorry, cannot find #{file}")
|
148
148
|
exit(1)
|
149
149
|
end
|
@@ -160,7 +160,7 @@ MODES:
|
|
160
160
|
end
|
161
161
|
|
162
162
|
opts.on('-p', '--plugin-path FILE', String, 'Plugin file') do |file|
|
163
|
-
unless File.
|
163
|
+
unless File.exist?(File.expand_path(file))
|
164
164
|
logger.error("Sorry, cannot find #{file}")
|
165
165
|
exit(1)
|
166
166
|
end
|
@@ -244,6 +244,7 @@ MODES:
|
|
244
244
|
'Until when should the host remain in testing.' +
|
245
245
|
' Anything parsable is ok, such as "5/18 4:35" or "16/9/13".'
|
246
246
|
) do |time|
|
247
|
+
# can make this an implicit rescue after we drop ruby 2.4
|
247
248
|
begin
|
248
249
|
options[:testing_until] = Time.parse(time)
|
249
250
|
rescue StandardError
|
@@ -305,6 +306,17 @@ MODES:
|
|
305
306
|
options[:roles] = roles
|
306
307
|
end
|
307
308
|
|
309
|
+
opts.on(
|
310
|
+
'-J', '--jumps JUMP',
|
311
|
+
'Uses ssh\'s `ProxyJump` support to ssh across bastion/jump hosts. ' +
|
312
|
+
'This is particularly useful in tunnel mode to test machines that ' +
|
313
|
+
'your workstatation doesn\'t have direct access to. The format is ' +
|
314
|
+
'the same as `ssh -J`: a comma-separated list of hosts to forward ' +
|
315
|
+
'through.'
|
316
|
+
) do |jumps|
|
317
|
+
options[:jumps] = jumps
|
318
|
+
end
|
319
|
+
|
308
320
|
opts.on('--really', 'Really do link-only. DANGEROUS!') do |r|
|
309
321
|
options[:really] = r
|
310
322
|
end
|
@@ -356,6 +368,15 @@ MODES:
|
|
356
368
|
options[:json] = true
|
357
369
|
end
|
358
370
|
|
371
|
+
opts.on(
|
372
|
+
'-w', '--windows-target',
|
373
|
+
'The target is a Windows machine. You will likely want to override ' +
|
374
|
+
'`test_timestamp` and `chef_config_path`, but *not* `config_file`. ' +
|
375
|
+
'Requires the target be running PowerShell >= 5.1 as the default shell.'
|
376
|
+
) do
|
377
|
+
options[:windows_target] = true
|
378
|
+
end
|
379
|
+
|
359
380
|
opts.separator ''
|
360
381
|
opts.separator 'Control local hook behavior with these options:'
|
361
382
|
|
@@ -389,7 +410,6 @@ MODES:
|
|
389
410
|
options[:skip_post_test_hook] = true
|
390
411
|
end
|
391
412
|
end
|
392
|
-
# rubocop:enable Metrics/BlockLength
|
393
413
|
|
394
414
|
if mode == 'help'
|
395
415
|
puts parser
|
@@ -398,7 +418,7 @@ MODES:
|
|
398
418
|
|
399
419
|
parser.parse!
|
400
420
|
|
401
|
-
if File.
|
421
|
+
if File.exist?(File.expand_path(options[:config_file]))
|
402
422
|
TasteTester::Config.from_file(File.expand_path(options[:config_file]))
|
403
423
|
end
|
404
424
|
TasteTester::Config.merge!(options)
|
@@ -407,7 +427,7 @@ MODES:
|
|
407
427
|
|
408
428
|
if TasteTester::Config.plugin_path
|
409
429
|
path = File.expand_path(TasteTester::Config.plugin_path)
|
410
|
-
unless File.
|
430
|
+
unless File.exist?(path)
|
411
431
|
logger.error("Plugin not found (#{path})")
|
412
432
|
exit(1)
|
413
433
|
end
|
@@ -447,5 +467,7 @@ MODES:
|
|
447
467
|
end
|
448
468
|
|
449
469
|
if $PROGRAM_NAME == __FILE__
|
450
|
-
|
470
|
+
module TasteTester
|
471
|
+
include TasteTester
|
472
|
+
end
|
451
473
|
end
|
data/lib/taste_tester/client.rb
CHANGED
@@ -20,6 +20,8 @@ require 'taste_tester/logging'
|
|
20
20
|
require 'between_meals/repo'
|
21
21
|
require 'between_meals/knife'
|
22
22
|
require 'between_meals/changeset'
|
23
|
+
require 'chef/log'
|
24
|
+
require 'chef/cookbook/chefignore'
|
23
25
|
|
24
26
|
module TasteTester
|
25
27
|
# Client side upload functionality
|
@@ -130,46 +132,52 @@ module TasteTester
|
|
130
132
|
def populate(stream, writer, path, destination)
|
131
133
|
full_path = File.join(File.join(TasteTester::Config.repo, path))
|
132
134
|
return unless File.directory?(full_path)
|
135
|
+
chefignores = Chef::Cookbook::Chefignore.new(full_path)
|
133
136
|
# everything is relative to the repo dir. chdir makes handling all the
|
134
137
|
# paths within this simpler
|
135
138
|
Dir.chdir(full_path) do
|
136
|
-
|
137
|
-
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
139
|
+
look_at = ['']
|
140
|
+
while (prefix = look_at.pop)
|
141
|
+
Dir.glob(File.join("#{prefix}**", '*'), File::FNM_DOTMATCH) do |p|
|
142
|
+
minus_first = p.split(
|
143
|
+
File::SEPARATOR,
|
144
|
+
)[1..-1].join(File::SEPARATOR)
|
145
|
+
next if chefignores.ignored?(p) ||
|
146
|
+
chefignores.ignored?(minus_first)
|
147
|
+
name = File.join(destination, p)
|
148
|
+
if File.directory?(p)
|
149
|
+
# we don't store directories in the tar, but we do want to follow
|
150
|
+
# top level symlinked directories as they are used to share
|
151
|
+
# cookbooks between codebases.
|
152
|
+
if minus_first == '' && File.symlink?(p)
|
153
|
+
look_at.push("#{p}#{File::SEPARATOR}")
|
154
|
+
end
|
155
|
+
elsif File.symlink?(p)
|
156
|
+
# tar handling of filenames > 100 characters gets complex. We'd
|
157
|
+
# use split_name from Minitar, but it's a private method. It's
|
158
|
+
# reasonable to assume that all symlink names in the bundle are
|
159
|
+
# less than 100 characters long. Long term, the version of minitar
|
160
|
+
# in chefdk should be upgraded.
|
161
|
+
fail 'Add support for long symlink paths' if name.size > 100
|
162
|
+
# The version of Minitar included in chefdk does not support
|
163
|
+
# symlinks directly. Therefore we use direct writes to the
|
164
|
+
# underlying stream to reproduce the symlinks
|
165
|
+
symlink = {
|
166
|
+
:name => name,
|
167
|
+
:mode => 0644,
|
168
|
+
:typeflag => '2',
|
169
|
+
:size => 0,
|
170
|
+
:linkname => File.readlink(p),
|
171
|
+
:prefix => '',
|
172
|
+
}
|
173
|
+
stream.write(Minitar::PosixHeader.new(symlink))
|
174
|
+
else
|
175
|
+
File.open(p, 'rb') do |r|
|
176
|
+
writer.add_file_simple(
|
177
|
+
name, :mode => 0644, :size => File.size(r)
|
178
|
+
) do |d, _opts|
|
179
|
+
IO.copy_stream(r, d)
|
180
|
+
end
|
173
181
|
end
|
174
182
|
end
|
175
183
|
end
|
@@ -206,11 +214,12 @@ module TasteTester
|
|
206
214
|
end
|
207
215
|
|
208
216
|
def full
|
217
|
+
logger.warn('Doing full upload')
|
209
218
|
if TasteTester::Config.bundle
|
210
219
|
bundle_upload
|
211
|
-
|
220
|
+
# only leave early if true (strictly bundle mode only)
|
221
|
+
return if TasteTester::Config.bundle == true
|
212
222
|
end
|
213
|
-
logger.warn('Doing full upload')
|
214
223
|
@knife.cookbook_upload_all
|
215
224
|
@knife.role_upload_all
|
216
225
|
@knife.databag_upload_all
|
@@ -220,7 +229,7 @@ module TasteTester
|
|
220
229
|
if TasteTester::Config.bundle
|
221
230
|
logger.info('No partial support for bundle mode, doing full upload')
|
222
231
|
bundle_upload
|
223
|
-
return
|
232
|
+
return if TasteTester::Config.bundle == true
|
224
233
|
end
|
225
234
|
logger.info('Doing differential upload from ' +
|
226
235
|
@server.latest_uploaded_ref)
|
@@ -158,7 +158,7 @@ module TasteTester
|
|
158
158
|
server = TasteTester::Server.new
|
159
159
|
hosts.each do |hostname|
|
160
160
|
host = TasteTester::Host.new(hostname, server)
|
161
|
-
host.
|
161
|
+
host.runchef
|
162
162
|
end
|
163
163
|
end
|
164
164
|
|
@@ -226,16 +226,17 @@ module TasteTester
|
|
226
226
|
|
227
227
|
changes = _find_changeset(repo)
|
228
228
|
|
229
|
-
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
|
233
|
-
|
229
|
+
# Perform preliminary impact analysis. By default, use Knife to find
|
230
|
+
# the roles dependent on modified cookbooks. Custom logic may provide
|
231
|
+
# additional information by defining the find_impact plugin method.
|
232
|
+
basic_impact = TasteTester::Hooks.find_impact(changes)
|
233
|
+
basic_impact ||= _find_roles(changes)
|
234
234
|
|
235
235
|
# Do any post processing required on the list of impacted roles, such
|
236
|
-
# as looking up hostnames associated with each role.
|
237
|
-
|
238
|
-
final_impact
|
236
|
+
# as looking up hostnames associated with each role. By default, pass
|
237
|
+
# the preliminary results through unmodified.
|
238
|
+
final_impact = TasteTester::Hooks.post_impact(basic_impact)
|
239
|
+
final_impact ||= basic_impact
|
239
240
|
|
240
241
|
# Print the calculated impact. If a print hook is defined that
|
241
242
|
# returns true, then the default print function is skipped.
|
@@ -283,7 +284,7 @@ module TasteTester
|
|
283
284
|
if TasteTester::Config.relative_cookbook_dirs.length > 1
|
284
285
|
logger.error('Knife deps does not support multiple cookbook paths.')
|
285
286
|
logger.error('Please flatten the cookbooks into a single directory' +
|
286
|
-
' or
|
287
|
+
' or define the find_impact method in a local plugin.')
|
287
288
|
exit(1)
|
288
289
|
end
|
289
290
|
|
@@ -292,7 +293,9 @@ module TasteTester
|
|
292
293
|
databags = Set.new(changes.databags)
|
293
294
|
|
294
295
|
if cookbooks.empty? && roles.empty?
|
295
|
-
|
296
|
+
unless TasteTester::Config.json
|
297
|
+
logger.warn('No cookbooks or roles have been modified.')
|
298
|
+
end
|
296
299
|
return Set.new
|
297
300
|
end
|
298
301
|
|
data/lib/taste_tester/config.rb
CHANGED
data/lib/taste_tester/hooks.rb
CHANGED
@@ -51,22 +51,22 @@ module TasteTester
|
|
51
51
|
# Find the set of roles dependent on the changed files.
|
52
52
|
# If returning something other than a set of roles, post_impact and/or
|
53
53
|
# print_impact should be specified to handle the output.
|
54
|
-
def self.
|
54
|
+
def self.find_impact(_changes); end
|
55
55
|
|
56
56
|
# Do stuff after we find impacted roles
|
57
|
-
# This should return a
|
58
|
-
#
|
59
|
-
# true to override the default output.
|
57
|
+
# This should return a JSON serializable object with the final impact
|
58
|
+
# assessment. You will probably also want to define a print_impact method
|
59
|
+
# which returns true to override the default output logic.
|
60
60
|
def self.post_impact(_impacted_roles); end
|
61
61
|
|
62
|
-
#
|
62
|
+
# Customize the printed output of impact
|
63
63
|
# If this method returns true, the default output will not be printed.
|
64
64
|
def self.print_impact(_final_impact); end
|
65
65
|
|
66
66
|
def self.get(file)
|
67
67
|
path = File.expand_path(file)
|
68
|
-
logger.warn("Loading plugin at #{path}")
|
69
|
-
unless File.
|
68
|
+
logger.warn("Loading plugin at #{path}") unless TasteTester::Config.json
|
69
|
+
unless File.exist?(path)
|
70
70
|
logger.error('Plugin file not found')
|
71
71
|
exit(1)
|
72
72
|
end
|
data/lib/taste_tester/host.rb
CHANGED
@@ -47,25 +47,11 @@ module TasteTester
|
|
47
47
|
def runchef
|
48
48
|
logger.warn("Running '#{TasteTester::Config.chef_client_command}' " +
|
49
49
|
"on #{@name}")
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
else
|
56
|
-
cmd += "\"#{cmds}\""
|
57
|
-
end
|
58
|
-
status = IO.popen(
|
59
|
-
cmd,
|
60
|
-
) do |io|
|
61
|
-
# rubocop:disable AssignmentInCondition
|
62
|
-
while line = io.gets
|
63
|
-
puts line.chomp!
|
64
|
-
end
|
65
|
-
# rubocop:enable AssignmentInCondition
|
66
|
-
io.close
|
67
|
-
$CHILD_STATUS.to_i
|
68
|
-
end
|
50
|
+
transport = get_transport
|
51
|
+
transport << TasteTester::Config.chef_client_command
|
52
|
+
|
53
|
+
io = IO.new(1)
|
54
|
+
status, = transport.run(io)
|
69
55
|
logger.warn("Finished #{TasteTester::Config.chef_client_command}" +
|
70
56
|
" on #{@name} with status #{status}")
|
71
57
|
if status.zero?
|
@@ -106,34 +92,24 @@ module TasteTester
|
|
106
92
|
# see if someone else is taste-testing
|
107
93
|
transport << we_testing
|
108
94
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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}/" +
|
122
|
-
"#{TasteTester::Config.chef_config}; true )"
|
95
|
+
if TasteTester::Config.windows_target
|
96
|
+
add_windows_test_cmds(transport, serialized_config)
|
97
|
+
else
|
98
|
+
add_sane_os_test_cmds(transport, serialized_config)
|
99
|
+
end
|
123
100
|
|
124
101
|
# look again to see if someone else is taste-testing. This is where
|
125
102
|
# we work out if we won or lost a race with another user.
|
126
103
|
transport << we_testing
|
127
104
|
|
128
|
-
transport.run
|
105
|
+
status, output = transport.run
|
129
106
|
|
130
|
-
case
|
107
|
+
case status
|
131
108
|
when 0
|
132
109
|
# no problem, keep going.
|
133
110
|
nil
|
134
111
|
when 42
|
135
|
-
fail TasteTester::Exceptions::AlreadyTestingError,
|
136
|
-
transport.output.chomp
|
112
|
+
fail TasteTester::Exceptions::AlreadyTestingError, output.chomp
|
137
113
|
else
|
138
114
|
transport.error!
|
139
115
|
end
|
@@ -157,18 +133,10 @@ module TasteTester
|
|
157
133
|
if TasteTester::Config.use_ssh_tunnels
|
158
134
|
TasteTester::Tunnel.kill(@name)
|
159
135
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
TasteTester::Config.chef_config,
|
165
|
-
"ln -vsf #{TasteTester::Config.chef_config_path}/client-prod.pem " +
|
166
|
-
"#{TasteTester::Config.chef_config_path}/client.pem",
|
167
|
-
"rm -vf #{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}",
|
168
|
-
"rm -vf #{TasteTester::Config.timestamp_file}",
|
169
|
-
'logger -t taste-tester Returning server to production',
|
170
|
-
].each do |cmd|
|
171
|
-
transport << cmd
|
136
|
+
if TasteTester::Config.windows_target
|
137
|
+
add_windows_untest_cmds(transport)
|
138
|
+
else
|
139
|
+
add_sane_os_untest_cmds(transport)
|
172
140
|
end
|
173
141
|
transport.run!
|
174
142
|
end
|
@@ -183,15 +151,30 @@ module TasteTester
|
|
183
151
|
# short circuits the test verb
|
184
152
|
# This is written as a squiggly heredoc so the indentation of the awk is
|
185
153
|
# preserved. Later we remove the newlines to make it a bit easier to read.
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
154
|
+
if TasteTester::Config.windows_target
|
155
|
+
shellcode = <<~ENDOFSHELLCODE
|
156
|
+
Get-Content #{config_file} | ForEach-Object {
|
157
|
+
if (\$_ -match "#{USER_PREAMBLE}" ) {
|
158
|
+
$user = \$_.Split()[-1]
|
159
|
+
if (\$user -ne "#{@user}") {
|
160
|
+
echo \$user
|
161
|
+
exit 42
|
162
|
+
}
|
163
|
+
}
|
191
164
|
}
|
192
|
-
|
193
|
-
|
194
|
-
|
165
|
+
ENDOFSHELLCODE
|
166
|
+
else
|
167
|
+
shellcode = <<~ENDOFSHELLCODE
|
168
|
+
awk "\\$0 ~ /^#{USER_PREAMBLE}/{
|
169
|
+
if (\\$NF != \\"#{@user}\\"){
|
170
|
+
print \\$NF;
|
171
|
+
exit 42
|
172
|
+
}
|
173
|
+
}" #{config_file}
|
174
|
+
ENDOFSHELLCODE
|
175
|
+
shellcode.chomp!
|
176
|
+
end
|
177
|
+
shellcode
|
195
178
|
end
|
196
179
|
|
197
180
|
def keeptesting
|
@@ -210,15 +193,147 @@ module TasteTester
|
|
210
193
|
|
211
194
|
private
|
212
195
|
|
196
|
+
# Sources must be 'registered' with the Eventlog, so check if we have
|
197
|
+
# registered and register if necessary
|
198
|
+
def create_eventlog_if_needed_cmd
|
199
|
+
get_src = 'Get-EventLog -LogName Application -source taste-tester 2>$null'
|
200
|
+
mk_src = 'New-EventLog -source "taste-tester" -LogName Application'
|
201
|
+
"if (-Not (#{get_src})) { #{mk_src} }"
|
202
|
+
end
|
203
|
+
|
204
|
+
# Remote testing commands for most OSes...
|
205
|
+
def add_sane_os_test_cmds(transport, serialized_config)
|
206
|
+
transport << 'logger -t taste-tester Moving server into taste-tester' +
|
207
|
+
" for #{@user}"
|
208
|
+
transport << touchcmd
|
209
|
+
# shell redirection is also racy, so make a temporary file first
|
210
|
+
transport << "tmpconf=$(mktemp #{TasteTester::Config.chef_config_path}/" +
|
211
|
+
"#{TASTE_TESTER_CONFIG}.TMPXXXXXX)"
|
212
|
+
transport << "/bin/echo -n \"#{serialized_config}\" | base64 --decode" +
|
213
|
+
' > "${tmpconf}"'
|
214
|
+
# then rename it to replace any existing file
|
215
|
+
transport << 'mv -f "${tmpconf}" ' +
|
216
|
+
"#{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}"
|
217
|
+
transport << "( ln -vsf #{TasteTester::Config.chef_config_path}" +
|
218
|
+
"/#{TASTE_TESTER_CONFIG} #{TasteTester::Config.chef_config_path}/" +
|
219
|
+
"#{TasteTester::Config.chef_config}; true )"
|
220
|
+
end
|
221
|
+
|
222
|
+
# Remote testing commands for Windows
|
223
|
+
def add_windows_test_cmds(transport, serialized_config)
|
224
|
+
# This is the closest equivalent to 'bash -x' - but if we put it on
|
225
|
+
# by default the way we do with linux it badly breaks our output. So only
|
226
|
+
# set it if we're in debug
|
227
|
+
#
|
228
|
+
# This isn't the most optimal place for this. It should be in ssh_util
|
229
|
+
# and we should jam this into the beggining of the cmds list we get,
|
230
|
+
# but this is early enough and good enough for now and we can think about
|
231
|
+
# that when we refactor tunnel.sh, ssh.sh and ssh_util.sh into one sane
|
232
|
+
# class.
|
233
|
+
if logger.level == Logger::DEBUG
|
234
|
+
transport << 'Set-PSDebug -trace 1'
|
235
|
+
end
|
236
|
+
|
237
|
+
ttconfig =
|
238
|
+
"#{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}"
|
239
|
+
realconfig = "#{TasteTester::Config.chef_config_path}/" +
|
240
|
+
TasteTester::Config.chef_config
|
241
|
+
[
|
242
|
+
create_eventlog_if_needed_cmd,
|
243
|
+
'Write-EventLog -LogName "Application" -Source "taste-tester" ' +
|
244
|
+
'-EventID 1 -EntryType Information ' +
|
245
|
+
"-Message \"Moving server into taste-tester for #{@user}\"",
|
246
|
+
touchcmd,
|
247
|
+
"$b64 = \"#{serialized_config}\"",
|
248
|
+
"$ttconfig = \"#{ttconfig}\"",
|
249
|
+
"$realconfig = \"#{realconfig}\"",
|
250
|
+
|
251
|
+
'$tmp64 = (New-TemporaryFile).name',
|
252
|
+
'$tmp = (New-TemporaryFile).name',
|
253
|
+
|
254
|
+
'$b64 | Out-File -Encoding ASCII $tmp64 -Force',
|
255
|
+
|
256
|
+
# Remove our tmp file before we write to it or certutil crashes...
|
257
|
+
"#{win_rm_f} $tmp",
|
258
|
+
'certutil -decode $tmp64 $tmp',
|
259
|
+
'mv $tmp $ttconfig -Force',
|
260
|
+
|
261
|
+
'New-Item -ItemType SymbolicLink -Value $ttconfig $realconfig -Force',
|
262
|
+
].each do |cmd|
|
263
|
+
transport << cmd
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
213
267
|
def touchcmd
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
268
|
+
if TasteTester::Config.windows_target
|
269
|
+
# There's no good touch equivalent in Windows. You can force
|
270
|
+
# creation of a new file, but that'll nuke it's contents, which if we're
|
271
|
+
# 'keeptesting'ing, then we'll loose the contents (PID and such).
|
272
|
+
# We can set the timestamp with Get-Item.creationtime, but it must exist
|
273
|
+
# if we're not gonna crash. So do both.
|
274
|
+
[
|
275
|
+
"$ts = \"#{TasteTester::Config.timestamp_file}\"",
|
276
|
+
'if (-Not (Test-Path $ts)) { New-Item -ItemType file $ts }',
|
277
|
+
'(Get-Item "$ts").LastWriteTime=("' +
|
278
|
+
"#{TasteTester::Config.testing_end_time}\")",
|
279
|
+
].join(';')
|
280
|
+
else
|
281
|
+
touch = Base64.encode64(
|
282
|
+
"if [ 'Darwin' = $(uname) ]; then touch -t \"$(date -r " +
|
283
|
+
"#{TasteTester::Config.testing_end_time.to_i} +'%Y%m%d%H%M.%S')\" " +
|
284
|
+
"#{TasteTester::Config.timestamp_file}; else touch --date \"$(date " +
|
285
|
+
"-d @#{TasteTester::Config.testing_end_time.to_i} +'%Y-%m-%d %T')\"" +
|
286
|
+
" #{TasteTester::Config.timestamp_file}; fi",
|
287
|
+
).delete("\n")
|
288
|
+
"/bin/echo -n '#{touch}' | base64 --decode | bash"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Remote untesting commands for Windows
|
293
|
+
def add_windows_untest_cmds(transport)
|
294
|
+
config_prod = TasteTester::Config.chef_config.split('.').join('-prod.')
|
295
|
+
tt_config =
|
296
|
+
"#{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}"
|
297
|
+
pem_file = "#{TasteTester::Config.chef_config_path}/client-prod.pem"
|
298
|
+
pem_link = "#{TasteTester::Config.chef_config_path}/client.pem"
|
299
|
+
|
300
|
+
[
|
301
|
+
'New-Item -ItemType SymbolicLink -Force -Value ' +
|
302
|
+
"#{TasteTester::Config.chef_config_path}/#{config_prod} " +
|
303
|
+
"#{TasteTester::Config.chef_config_path}/" +
|
304
|
+
TasteTester::Config.chef_config,
|
305
|
+
'New-Item -ItemType SymbolicLink -Force -Value ' +
|
306
|
+
"#{pem_file} #{pem_link}",
|
307
|
+
"#{win_rm_f} #{tt_config}",
|
308
|
+
"#{win_rm_f} #{TasteTester::Config.timestamp_file}",
|
309
|
+
create_eventlog_if_needed_cmd,
|
310
|
+
'Write-EventLog -LogName "Application" -Source "taste-tester" ' +
|
311
|
+
'-EventID 4 -EntryType Information -Message "Returning server ' +
|
312
|
+
'to production"',
|
313
|
+
].each do |cmd|
|
314
|
+
transport << cmd
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# Remote untesting commands for most OSes...
|
319
|
+
def add_sane_os_untest_cmds(transport)
|
320
|
+
config_prod = TasteTester::Config.chef_config.split('.').join('-prod.')
|
321
|
+
[
|
322
|
+
"ln -vsf #{TasteTester::Config.chef_config_path}/#{config_prod} " +
|
323
|
+
"#{TasteTester::Config.chef_config_path}/" +
|
324
|
+
TasteTester::Config.chef_config,
|
325
|
+
"ln -vsf #{TasteTester::Config.chef_config_path}/client-prod.pem " +
|
326
|
+
"#{TasteTester::Config.chef_config_path}/client.pem",
|
327
|
+
"rm -vf #{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}",
|
328
|
+
"rm -vf #{TasteTester::Config.timestamp_file}",
|
329
|
+
'logger -t taste-tester Returning server to production',
|
330
|
+
].each do |cmd|
|
331
|
+
transport << cmd
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def win_rm_f
|
336
|
+
'Remove-Item -Force -ErrorAction SilentlyContinue'
|
222
337
|
end
|
223
338
|
|
224
339
|
def config
|
@@ -310,7 +425,7 @@ module TasteTester
|
|
310
425
|
chef_repo_path taste_tester_dest
|
311
426
|
ENDOFSCRIPT
|
312
427
|
end
|
313
|
-
|
428
|
+
ttconfig
|
314
429
|
end
|
315
430
|
end
|
316
431
|
end
|