taste_tester 0.0.14 → 0.0.19
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![CircleCI](https://circleci.com/gh/facebook/taste-tester.svg?style=svg)](https://circleci.com/gh/facebook/taste-tester)
|
3
|
+
![Continuous Integration](https://github.com/facebook/taste-tester/workflows/Continuous%20Integration/badge.svg?event=push)
|
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
|