taste_tester 0.0.16 → 0.0.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/bin/taste-tester +18 -8
- data/lib/taste_tester/client.rb +42 -47
- data/lib/taste_tester/config.rb +1 -0
- data/lib/taste_tester/hooks.rb +1 -1
- data/lib/taste_tester/host.rb +167 -43
- data/lib/taste_tester/server.rb +1 -1
- data/lib/taste_tester/ssh.rb +3 -33
- data/lib/taste_tester/ssh_util.rb +127 -0
- data/lib/taste_tester/state.rb +1 -1
- data/lib/taste_tester/tunnel.rb +91 -41
- data/scripts/taste-untester +8 -8
- data/scripts/taste-untester.ps1 +85 -0
- metadata +19 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01cc261c810b1efa71e7eba767be42c7292a2e7ebfa1f0ad2a81cb0aab465504
|
4
|
+
data.tar.gz: 2891b4d9a14b03183b24dc2fd3baada23b3a86e0ba741eb2ff43ed4e9bb15ef0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa9dee7d09f5a0ffd03c63d2bd96cabde536b498fb8f4efeab8fbfd564ecb21166954200f8d62702833b3c7977aba0b979e920e93d047a27e9ea4d5538d88dbf
|
7
|
+
data.tar.gz: 593d8b054ff209d1c5ed116d90a00bca9bb2a64a3d8e2c576e068e91321d286a3e2b30347179d22dafc09ca4525c70c2d49351e8b838fa3c8063ed5a006a89e4
|
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!
|
@@ -106,7 +105,7 @@ The following options are also available:
|
|
106
105
|
* base_dir - The directory in the repo under which to find chef configs.
|
107
106
|
Default: `chef`
|
108
107
|
* cookbook_dirs - An array of cookbook directories relative to base_dir.
|
109
|
-
Default: `['cookbooks']
|
108
|
+
Default: `['cookbooks']`
|
110
109
|
* role_dir - A directory of roles, relative to base_dir. Default: `roles`
|
111
110
|
* databag_dir - A directory of databags, relative to base_dir.
|
112
111
|
Default: `databags`
|
@@ -123,7 +122,6 @@ The following options are also available:
|
|
123
122
|
* impact - analyze local changes to determine which hosts/roles to test.
|
124
123
|
Default: false
|
125
124
|
|
126
|
-
|
127
125
|
## Plugin
|
128
126
|
|
129
127
|
The plugin should be a ruby file which defines several class methods. It is
|
@@ -181,10 +179,11 @@ Custom output of calculated impact, useful if defining either of the other
|
|
181
179
|
impact hooks. Must return a truthy value to prevent the default output from
|
182
180
|
printing.
|
183
181
|
|
184
|
-
|
182
|
+
## Plugin example
|
185
183
|
|
186
184
|
This is an example `/etc/taste-tester-plugin.rb` to add a user-defined string
|
187
185
|
to `client-taste-tester.rb` on the remote system:
|
186
|
+
|
188
187
|
```
|
189
188
|
Hooks.class_eval do
|
190
189
|
def self.test_remote_client_rb_extra_code(_hostname)
|
@@ -195,6 +194,7 @@ Hooks.class_eval do
|
|
195
194
|
end
|
196
195
|
end
|
197
196
|
```
|
197
|
+
|
198
198
|
Be sure to pass this plugin file with `-p` on the command line or set it as
|
199
199
|
`plugin_path` in your `taste-tester-config.rb` file.
|
200
200
|
|
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
|
)
|
@@ -136,7 +136,6 @@ MODES:
|
|
136
136
|
end
|
137
137
|
|
138
138
|
options = { :config_file => TasteTester::Config.config_file }
|
139
|
-
# rubocop:disable Metrics/BlockLength
|
140
139
|
parser = OptionParser.new do |opts|
|
141
140
|
opts.banner = description
|
142
141
|
|
@@ -144,7 +143,7 @@ MODES:
|
|
144
143
|
opts.separator 'Global options:'.upcase
|
145
144
|
|
146
145
|
opts.on('-c', '--config FILE', 'Config file') do |file|
|
147
|
-
unless File.
|
146
|
+
unless File.exist?(File.expand_path(file))
|
148
147
|
logger.error("Sorry, cannot find #{file}")
|
149
148
|
exit(1)
|
150
149
|
end
|
@@ -161,7 +160,7 @@ MODES:
|
|
161
160
|
end
|
162
161
|
|
163
162
|
opts.on('-p', '--plugin-path FILE', String, 'Plugin file') do |file|
|
164
|
-
unless File.
|
163
|
+
unless File.exist?(File.expand_path(file))
|
165
164
|
logger.error("Sorry, cannot find #{file}")
|
166
165
|
exit(1)
|
167
166
|
end
|
@@ -245,6 +244,7 @@ MODES:
|
|
245
244
|
'Until when should the host remain in testing.' +
|
246
245
|
' Anything parsable is ok, such as "5/18 4:35" or "16/9/13".'
|
247
246
|
) do |time|
|
247
|
+
# can make this an implicit rescue after we drop ruby 2.4
|
248
248
|
begin
|
249
249
|
options[:testing_until] = Time.parse(time)
|
250
250
|
rescue StandardError
|
@@ -368,6 +368,15 @@ MODES:
|
|
368
368
|
options[:json] = true
|
369
369
|
end
|
370
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
|
+
|
371
380
|
opts.separator ''
|
372
381
|
opts.separator 'Control local hook behavior with these options:'
|
373
382
|
|
@@ -401,7 +410,6 @@ MODES:
|
|
401
410
|
options[:skip_post_test_hook] = true
|
402
411
|
end
|
403
412
|
end
|
404
|
-
# rubocop:enable Metrics/BlockLength
|
405
413
|
|
406
414
|
if mode == 'help'
|
407
415
|
puts parser
|
@@ -410,7 +418,7 @@ MODES:
|
|
410
418
|
|
411
419
|
parser.parse!
|
412
420
|
|
413
|
-
if File.
|
421
|
+
if File.exist?(File.expand_path(options[:config_file]))
|
414
422
|
TasteTester::Config.from_file(File.expand_path(options[:config_file]))
|
415
423
|
end
|
416
424
|
TasteTester::Config.merge!(options)
|
@@ -419,7 +427,7 @@ MODES:
|
|
419
427
|
|
420
428
|
if TasteTester::Config.plugin_path
|
421
429
|
path = File.expand_path(TasteTester::Config.plugin_path)
|
422
|
-
unless File.
|
430
|
+
unless File.exist?(path)
|
423
431
|
logger.error("Plugin not found (#{path})")
|
424
432
|
exit(1)
|
425
433
|
end
|
@@ -459,5 +467,7 @@ MODES:
|
|
459
467
|
end
|
460
468
|
|
461
469
|
if $PROGRAM_NAME == __FILE__
|
462
|
-
|
470
|
+
module TasteTester
|
471
|
+
include TasteTester
|
472
|
+
end
|
463
473
|
end
|
data/lib/taste_tester/client.rb
CHANGED
@@ -136,53 +136,48 @@ module TasteTester
|
|
136
136
|
# everything is relative to the repo dir. chdir makes handling all the
|
137
137
|
# paths within this simpler
|
138
138
|
Dir.chdir(full_path) do
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
File.open(p, 'rb') do |r|
|
182
|
-
writer.add_file_simple(
|
183
|
-
name, :mode => 0644, :size => File.size(r)
|
184
|
-
) do |d, _opts|
|
185
|
-
IO.copy_stream(r, d)
|
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
|
186
181
|
end
|
187
182
|
end
|
188
183
|
end
|
data/lib/taste_tester/config.rb
CHANGED
data/lib/taste_tester/hooks.rb
CHANGED
@@ -66,7 +66,7 @@ module TasteTester
|
|
66
66
|
def self.get(file)
|
67
67
|
path = File.expand_path(file)
|
68
68
|
logger.warn("Loading plugin at #{path}") unless TasteTester::Config.json
|
69
|
-
unless File.
|
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
@@ -92,20 +92,11 @@ module TasteTester
|
|
92
92
|
# see if someone else is taste-testing
|
93
93
|
transport << we_testing
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
"#{TASTE_TESTER_CONFIG}.TMPXXXXXX)"
|
101
|
-
transport << "/bin/echo -n \"#{serialized_config}\" | base64 --decode" +
|
102
|
-
' > "${tmpconf}"'
|
103
|
-
# then rename it to replace any existing file
|
104
|
-
transport << 'mv -f "${tmpconf}" ' +
|
105
|
-
"#{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}"
|
106
|
-
transport << "( ln -vsf #{TasteTester::Config.chef_config_path}" +
|
107
|
-
"/#{TASTE_TESTER_CONFIG} #{TasteTester::Config.chef_config_path}/" +
|
108
|
-
"#{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
|
109
100
|
|
110
101
|
# look again to see if someone else is taste-testing. This is where
|
111
102
|
# we work out if we won or lost a race with another user.
|
@@ -142,18 +133,10 @@ module TasteTester
|
|
142
133
|
if TasteTester::Config.use_ssh_tunnels
|
143
134
|
TasteTester::Tunnel.kill(@name)
|
144
135
|
end
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
TasteTester::Config.chef_config,
|
150
|
-
"ln -vsf #{TasteTester::Config.chef_config_path}/client-prod.pem " +
|
151
|
-
"#{TasteTester::Config.chef_config_path}/client.pem",
|
152
|
-
"rm -vf #{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}",
|
153
|
-
"rm -vf #{TasteTester::Config.timestamp_file}",
|
154
|
-
'logger -t taste-tester Returning server to production',
|
155
|
-
].each do |cmd|
|
156
|
-
transport << cmd
|
136
|
+
if TasteTester::Config.windows_target
|
137
|
+
add_windows_untest_cmds(transport)
|
138
|
+
else
|
139
|
+
add_sane_os_untest_cmds(transport)
|
157
140
|
end
|
158
141
|
transport.run!
|
159
142
|
end
|
@@ -168,15 +151,31 @@ module TasteTester
|
|
168
151
|
# short circuits the test verb
|
169
152
|
# This is written as a squiggly heredoc so the indentation of the awk is
|
170
153
|
# preserved. Later we remove the newlines to make it a bit easier to read.
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
+
}
|
176
164
|
}
|
177
|
-
|
178
|
-
|
179
|
-
|
165
|
+
ENDOFSHELLCODE
|
166
|
+
shellcode.chomp
|
167
|
+
else
|
168
|
+
shellcode = <<~ENDOFSHELLCODE
|
169
|
+
awk "\\$0 ~ /^#{USER_PREAMBLE}/{
|
170
|
+
if (\\$NF != \\"#{@user}\\"){
|
171
|
+
print \\$NF;
|
172
|
+
exit 42
|
173
|
+
}
|
174
|
+
}" #{config_file}
|
175
|
+
ENDOFSHELLCODE
|
176
|
+
shellcode.delete("\n")
|
177
|
+
end
|
178
|
+
shellcode
|
180
179
|
end
|
181
180
|
|
182
181
|
def keeptesting
|
@@ -195,15 +194,140 @@ module TasteTester
|
|
195
194
|
|
196
195
|
private
|
197
196
|
|
197
|
+
# Sources must be 'registered' with the Eventlog, so check if we have
|
198
|
+
# registered and register if necessary
|
199
|
+
def create_eventlog_if_needed_cmd
|
200
|
+
get_src = 'Get-EventLog -LogName Application -source taste-tester 2>$null'
|
201
|
+
mk_src = 'New-EventLog -source "taste-tester" -LogName Application'
|
202
|
+
"if (-Not (#{get_src})) { #{mk_src} }"
|
203
|
+
end
|
204
|
+
|
205
|
+
# Remote testing commands for most OSes...
|
206
|
+
def add_sane_os_test_cmds(transport, serialized_config)
|
207
|
+
transport << 'logger -t taste-tester Moving server into taste-tester' +
|
208
|
+
" for #{@user}"
|
209
|
+
transport << touchcmd
|
210
|
+
# shell redirection is also racy, so make a temporary file first
|
211
|
+
transport << "tmpconf=$(mktemp #{TasteTester::Config.chef_config_path}/" +
|
212
|
+
"#{TASTE_TESTER_CONFIG}.TMPXXXXXX)"
|
213
|
+
transport << "/bin/echo -n \"#{serialized_config}\" | base64 --decode" +
|
214
|
+
' > "${tmpconf}"'
|
215
|
+
# then rename it to replace any existing file
|
216
|
+
transport << 'mv -f "${tmpconf}" ' +
|
217
|
+
"#{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}"
|
218
|
+
transport << "( ln -vsf #{TasteTester::Config.chef_config_path}" +
|
219
|
+
"/#{TASTE_TESTER_CONFIG} #{TasteTester::Config.chef_config_path}/" +
|
220
|
+
"#{TasteTester::Config.chef_config}; true )"
|
221
|
+
end
|
222
|
+
|
223
|
+
# Remote testing commands for Windows
|
224
|
+
def add_windows_test_cmds(transport, serialized_config)
|
225
|
+
# This is the closest equivalent to 'bash -x' - but if we put it on
|
226
|
+
# by default the way we do with linux it badly breaks our output. So only
|
227
|
+
# set it if we're in debug
|
228
|
+
#
|
229
|
+
# This isn't the most optimal place for this. It should be in ssh_util
|
230
|
+
# and we should jam this into the beggining of the cmds list we get,
|
231
|
+
# but this is early enough and good enough for now and we can think about
|
232
|
+
# that when we refactor tunnel.sh, ssh.sh and ssh_util.sh into one sane
|
233
|
+
# class.
|
234
|
+
if logger.level == Logger::DEBUG
|
235
|
+
transport << 'Set-PSDebug -trace 1'
|
236
|
+
end
|
237
|
+
|
238
|
+
ttconfig =
|
239
|
+
"#{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}"
|
240
|
+
realconfig = "#{TasteTester::Config.chef_config_path}/" +
|
241
|
+
TasteTester::Config.chef_config
|
242
|
+
[
|
243
|
+
create_eventlog_if_needed_cmd,
|
244
|
+
'Write-EventLog -LogName "Application" -Source "taste-tester" ' +
|
245
|
+
'-EventID 1 -EntryType Information ' +
|
246
|
+
"-Message \"Moving server into taste-tester for #{@user}\"",
|
247
|
+
touchcmd,
|
248
|
+
"$b64 = \"#{serialized_config}\"",
|
249
|
+
"$ttconfig = \"#{ttconfig}\"",
|
250
|
+
"$realconfig = \"#{realconfig}\"",
|
251
|
+
|
252
|
+
'$tmp64 = (New-TemporaryFile).name',
|
253
|
+
'$tmp = (New-TemporaryFile).name',
|
254
|
+
|
255
|
+
'$b64 | Out-File -Encoding ASCII $tmp64 -Force',
|
256
|
+
|
257
|
+
# Remove our tmp file before we write to it or certutil crashes...
|
258
|
+
'if (Test-Path $tmp) { rm $tmp }',
|
259
|
+
'certutil -decode $tmp64 $tmp',
|
260
|
+
'mv $tmp $ttconfig -Force',
|
261
|
+
|
262
|
+
'New-Item -ItemType SymbolicLink -Value $ttconfig $realconfig -Force',
|
263
|
+
].each do |cmd|
|
264
|
+
transport << cmd
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
198
268
|
def touchcmd
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
269
|
+
if TasteTester::Config.windows_target
|
270
|
+
# There's no good touch equivalent in Windows. You can force
|
271
|
+
# creation of a new file, but that'll nuke it's contents, which if we're
|
272
|
+
# 'keeptesting'ing, then we'll loose the contents (PID and such).
|
273
|
+
# We can set the timestamp with Get-Item.creationtime, but it must exist
|
274
|
+
# if we're not gonna crash. So do both.
|
275
|
+
[
|
276
|
+
"$ts = \"#{TasteTester::Config.timestamp_file}\"",
|
277
|
+
'if (-Not (Test-Path $ts)) { New-Item -ItemType file $ts }',
|
278
|
+
'(Get-Item "$ts").LastWriteTime=("' +
|
279
|
+
"#{TasteTester::Config.testing_end_time}\")",
|
280
|
+
].join(';')
|
281
|
+
else
|
282
|
+
touch = Base64.encode64(
|
283
|
+
"if [ 'Darwin' = $(uname) ]; then touch -t \"$(date -r " +
|
284
|
+
"#{TasteTester::Config.testing_end_time.to_i} +'%Y%m%d%H%M.%S')\" " +
|
285
|
+
"#{TasteTester::Config.timestamp_file}; else touch --date \"$(date " +
|
286
|
+
"-d @#{TasteTester::Config.testing_end_time.to_i} +'%Y-%m-%d %T')\"" +
|
287
|
+
" #{TasteTester::Config.timestamp_file}; fi",
|
288
|
+
).delete("\n")
|
289
|
+
"/bin/echo -n '#{touch}' | base64 --decode | bash"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Remote untesting commands for Windows
|
294
|
+
def add_windows_untest_cmds(transport)
|
295
|
+
config_prod = TasteTester::Config.chef_config.split('.').join('-prod.')
|
296
|
+
[
|
297
|
+
'New-Item -ItemType SymbolicLink -Force -Value ' +
|
298
|
+
"#{TasteTester::Config.chef_config_path}/#{config_prod} " +
|
299
|
+
"#{TasteTester::Config.chef_config_path}/" +
|
300
|
+
TasteTester::Config.chef_config,
|
301
|
+
'New-Item -ItemType SymbolicLink -Force -Value ' +
|
302
|
+
"#{TasteTester::Config.chef_config_path}/client-prod.pem " +
|
303
|
+
"#{TasteTester::Config.chef_config_path}/client.pem",
|
304
|
+
'rm -Force ' +
|
305
|
+
"#{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}",
|
306
|
+
"rm -Force #{TasteTester::Config.timestamp_file}",
|
307
|
+
create_eventlog_if_needed_cmd,
|
308
|
+
'Write-EventLog -LogName "Application" -Source "taste-tester" ' +
|
309
|
+
'-EventID 4 -EntryType Information -Message "Returning server ' +
|
310
|
+
'to production"',
|
311
|
+
].each do |cmd|
|
312
|
+
transport << cmd
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Remote untesting commands for most OSes...
|
317
|
+
def add_sane_os_untest_cmds(transport)
|
318
|
+
config_prod = TasteTester::Config.chef_config.split('.').join('-prod.')
|
319
|
+
[
|
320
|
+
"ln -vsf #{TasteTester::Config.chef_config_path}/#{config_prod} " +
|
321
|
+
"#{TasteTester::Config.chef_config_path}/" +
|
322
|
+
TasteTester::Config.chef_config,
|
323
|
+
"ln -vsf #{TasteTester::Config.chef_config_path}/client-prod.pem " +
|
324
|
+
"#{TasteTester::Config.chef_config_path}/client.pem",
|
325
|
+
"rm -vf #{TasteTester::Config.chef_config_path}/#{TASTE_TESTER_CONFIG}",
|
326
|
+
"rm -vf #{TasteTester::Config.timestamp_file}",
|
327
|
+
'logger -t taste-tester Returning server to production',
|
328
|
+
].each do |cmd|
|
329
|
+
transport << cmd
|
330
|
+
end
|
207
331
|
end
|
208
332
|
|
209
333
|
def config
|
@@ -295,7 +419,7 @@ module TasteTester
|
|
295
419
|
chef_repo_path taste_tester_dest
|
296
420
|
ENDOFSCRIPT
|
297
421
|
end
|
298
|
-
|
422
|
+
ttconfig
|
299
423
|
end
|
300
424
|
end
|
301
425
|
end
|
data/lib/taste_tester/server.rb
CHANGED
@@ -161,7 +161,7 @@ module TasteTester
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def start_chef_zero
|
164
|
-
File.unlink(@log_file) if File.
|
164
|
+
File.unlink(@log_file) if File.exist?(@log_file)
|
165
165
|
@state.update({
|
166
166
|
:port => TasteTester::Config.chef_port,
|
167
167
|
:ssl => TasteTester::Config.use_ssl,
|
data/lib/taste_tester/ssh.rb
CHANGED
@@ -15,12 +15,14 @@
|
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
17
|
require 'taste_tester/exceptions'
|
18
|
+
require 'taste_tester/ssh_util'
|
18
19
|
|
19
20
|
module TasteTester
|
20
21
|
# Thin ssh wrapper
|
21
22
|
class SSH
|
22
23
|
include TasteTester::Logging
|
23
24
|
include BetweenMeals::Util
|
25
|
+
include TasteTester::SSH::Util
|
24
26
|
|
25
27
|
def initialize(host, tunnel = false)
|
26
28
|
@host = host
|
@@ -45,45 +47,13 @@ module TasteTester
|
|
45
47
|
error!
|
46
48
|
end
|
47
49
|
|
48
|
-
def error!
|
49
|
-
error = <<~ERRORMESSAGE
|
50
|
-
SSH returned error while connecting to #{TasteTester::Config.user}@#{@host}
|
51
|
-
The host might be broken or your SSH access is not working properly
|
52
|
-
Try doing
|
53
|
-
|
54
|
-
#{ssh_base_cmd} -v #{TasteTester::Config.user}@#{@host}
|
55
|
-
|
56
|
-
to see if ssh connection is good.
|
57
|
-
If ssh works, add '-v' key to taste-tester to see the list of commands it's
|
58
|
-
trying to execute, and try to run them manually on destination host
|
59
|
-
ERRORMESSAGE
|
60
|
-
logger.error(error)
|
61
|
-
fail TasteTester::Exceptions::SshError
|
62
|
-
end
|
63
|
-
|
64
50
|
private
|
65
51
|
|
66
|
-
def ssh_base_cmd
|
67
|
-
jumps = TasteTester::Config.jumps ? "-J #{TasteTester::Config.jumps}" : ''
|
68
|
-
"#{TasteTester::Config.ssh_command} #{jumps}"
|
69
|
-
end
|
70
|
-
|
71
52
|
def cmd
|
72
53
|
@cmds.each do |cmd|
|
73
54
|
logger.info("Will run: '#{cmd}' on #{@host}")
|
74
55
|
end
|
75
|
-
|
76
|
-
cmd = "#{ssh_base_cmd} -T -o BatchMode=yes " +
|
77
|
-
"-o ConnectTimeout=#{TasteTester::Config.ssh_connect_timeout} " +
|
78
|
-
"#{TasteTester::Config.user}@#{@host} "
|
79
|
-
cc = Base64.encode64(cmds).delete("\n")
|
80
|
-
cmd += "\"echo '#{cc}' | base64 --decode"
|
81
|
-
if TasteTester::Config.user != 'root'
|
82
|
-
cmd += ' | sudo bash -x"'
|
83
|
-
else
|
84
|
-
cmd += ' | bash -x"'
|
85
|
-
end
|
86
|
-
cmd
|
56
|
+
build_ssh_cmd(ssh_base_cmd, @cmds)
|
87
57
|
end
|
88
58
|
end
|
89
59
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module TasteTester
|
2
|
+
class SSH
|
3
|
+
module Util
|
4
|
+
def ssh_base_cmd
|
5
|
+
jumps = TasteTester::Config.jumps ?
|
6
|
+
"-J #{TasteTester::Config.jumps}" : ''
|
7
|
+
"#{TasteTester::Config.ssh_command} #{jumps} -T -o BatchMode=yes " +
|
8
|
+
'-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
|
9
|
+
"-o ConnectTimeout=#{TasteTester::Config.ssh_connect_timeout} " +
|
10
|
+
"#{TasteTester::Config.user}@#{@host} "
|
11
|
+
end
|
12
|
+
|
13
|
+
def error!
|
14
|
+
error = <<~ERRORMESSAGE
|
15
|
+
SSH returned error while connecting to #{TasteTester::Config.user}@#{@host}
|
16
|
+
The host might be broken or your SSH access is not working properly
|
17
|
+
Try doing
|
18
|
+
|
19
|
+
#{ssh_base_cmd} -v
|
20
|
+
|
21
|
+
to see if ssh connection is good.
|
22
|
+
If ssh works, add '-v' key to taste-tester to see the list of commands it's
|
23
|
+
trying to execute, and try to run them manually on destination host
|
24
|
+
ERRORMESSAGE
|
25
|
+
logger.error(error)
|
26
|
+
fail TasteTester::Exceptions::SshError
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_ssh_cmd(ssh, command_list)
|
30
|
+
if TasteTester::Config.windows_target
|
31
|
+
# Powershell has no `&&`. So originally we looked into joining the
|
32
|
+
# various commands with `; if ($LASTEXITCODE -ne 0) { exit 42 }; `
|
33
|
+
# except that it turns out lots of Powershell commands don't set
|
34
|
+
# $LASTEXITCODE and so that crashes a lot.
|
35
|
+
#
|
36
|
+
# There is an `-and`, but it only works if you group things together
|
37
|
+
# with `()`, but that loses any output.
|
38
|
+
#
|
39
|
+
# Technically in the latest preview of Powershell 7, `&&` exists, but
|
40
|
+
# we cannot rely on this.
|
41
|
+
#
|
42
|
+
# So here we are. Thanks Windows Team.
|
43
|
+
#
|
44
|
+
# Anyway, what we *really* care about is that we exit if we_testing()
|
45
|
+
# errors out, and on Windows, we can do that straight from the
|
46
|
+
# powershell we generate there (we're not forking off awk), so the
|
47
|
+
# `&&` isn't as critical. It's still a bummer that we continue on
|
48
|
+
# if one of the commands fails, but... Well, it's Windows,
|
49
|
+
# whatchyagonnado?
|
50
|
+
|
51
|
+
cmds = command_list.join(' ; ')
|
52
|
+
else
|
53
|
+
cmds = command_list.join(' && ')
|
54
|
+
end
|
55
|
+
cmd = ssh
|
56
|
+
cc = Base64.encode64(cmds).delete("\n")
|
57
|
+
if TasteTester::Config.windows_target
|
58
|
+
|
59
|
+
# This is pretty horrible, but because there's no way I can find to
|
60
|
+
# take base64 as stdin and output text, we end up having to do use
|
61
|
+
# these PS functions. But they're going to pass through *both* bash
|
62
|
+
# *and* powershell, so in order to preserve the quotes, it gets
|
63
|
+
# pretty ugly.
|
64
|
+
#
|
65
|
+
# The tldr here is that in shell you can't escape quotes you're
|
66
|
+
# using to quote something. So if you use single quotes, there's no
|
67
|
+
# way to escape a single quote inside, and same with double-quotes.
|
68
|
+
# As such we switch between quote-styles as necessary. As long as the
|
69
|
+
# strings are back-to-back, shell handles this well. To make this
|
70
|
+
# clear, imagine you want to echo this:
|
71
|
+
# '"'"
|
72
|
+
# Exactly like that. You would quote the first single quotes in double
|
73
|
+
# quotes: "'"
|
74
|
+
# Then the double quotes in single quotes: '"'
|
75
|
+
# Now repeat twice and you get: echo "'"'"'"'"'"'
|
76
|
+
# And that works reliably.
|
77
|
+
#
|
78
|
+
# We're doing the same thing here. What we want on the other side of
|
79
|
+
# the ssh is:
|
80
|
+
# [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String('...'))
|
81
|
+
#
|
82
|
+
# But for this to work right the command we pass to SSH has to be in
|
83
|
+
# single quotes too. For simplicity lets call those two functions
|
84
|
+
# above GetString() and Base64(). So we'll start with:
|
85
|
+
# ssh host 'GetString(Base64('
|
86
|
+
# We've closed that string, now we add the single quote we want there,
|
87
|
+
# as well as the stuff inside of those double quotes, so we'll add:
|
88
|
+
# '#{cc}'))
|
89
|
+
# but that must be in double quotes since we're using single quotes.
|
90
|
+
# Put that together:
|
91
|
+
# ssh host 'GetString(Base64('"'#{cc}'))"
|
92
|
+
# ^-----------------^^---------^
|
93
|
+
# string 1 string2
|
94
|
+
# No we're doing with needing single quotes inside of our string, go
|
95
|
+
# back to using single-quotes so no variables get interpolated. We now
|
96
|
+
# add: ' | powershell.exe -c -; exit $LASTEXITCODE'
|
97
|
+
# ssh host 'GetString(Base64('"'#{cc}'))"' | powershell.exe ...'
|
98
|
+
# ^-----------------^^---------^^---------------------^
|
99
|
+
#
|
100
|
+
# More than you ever wanted to know about shell. You're welcome.
|
101
|
+
#
|
102
|
+
# But now we have to put it inside of a ruby string, :)
|
103
|
+
|
104
|
+
# just for readability, put these crazy function names inside of
|
105
|
+
# variables
|
106
|
+
fun1 = '[Text.Encoding]::Utf8.GetString'
|
107
|
+
fun2 = '[Convert]::FromBase64String'
|
108
|
+
cmd += "'#{fun1}(#{fun2}('\"'#{cc}'))\"' | "
|
109
|
+
# ^----------------^ ^----------^^---
|
110
|
+
# single-q double-q single-q
|
111
|
+
# string 1 string2 string3
|
112
|
+
cmd += 'powershell.exe -c -; exit $LASTEXITCODE\''
|
113
|
+
# ----------------------------------------^
|
114
|
+
# continued string3
|
115
|
+
else
|
116
|
+
cmd += "\"echo '#{cc}' | base64 --decode"
|
117
|
+
if TasteTester::Config.user != 'root'
|
118
|
+
cmd += ' | sudo bash -x"'
|
119
|
+
else
|
120
|
+
cmd += ' | bash -x"'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
cmd
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/taste_tester/state.rb
CHANGED
data/lib/taste_tester/tunnel.rb
CHANGED
@@ -14,11 +14,16 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
+
require 'taste_tester/logging'
|
18
|
+
require 'between_meals/util'
|
19
|
+
require 'taste_tester/ssh_util'
|
20
|
+
|
17
21
|
module TasteTester
|
18
22
|
# Thin ssh tunnel wrapper
|
19
23
|
class Tunnel
|
20
24
|
include TasteTester::Logging
|
21
25
|
include BetweenMeals::Util
|
26
|
+
include TasteTester::SSH::Util
|
22
27
|
|
23
28
|
attr_reader :port
|
24
29
|
|
@@ -33,18 +38,100 @@ module TasteTester
|
|
33
38
|
exec!(cmd, logger)
|
34
39
|
rescue StandardError => e
|
35
40
|
logger.error "Failed bringing up ssh tunnel: #{e}"
|
36
|
-
|
41
|
+
error!
|
37
42
|
end
|
38
43
|
|
39
44
|
def cmd
|
40
|
-
|
45
|
+
if TasteTester::Config.windows_target
|
46
|
+
cmds = windows_tunnel_cmd
|
47
|
+
else
|
48
|
+
cmds = sane_os_tunnel_cmd
|
49
|
+
end
|
50
|
+
|
51
|
+
# As great as it would be to have ExitOnForwardFailure=yes,
|
52
|
+
# we had multiple cases of tunnels dying
|
53
|
+
# if -f and ExitOnForwardFailure are used together.
|
54
|
+
# In most cases the first request from chef was "breaking" the tunnel,
|
55
|
+
# in a way that port was still open, but subsequent requests were hanging.
|
56
|
+
# This is reproducible and should be looked into.
|
57
|
+
cmd = "#{ssh_base_cmd} -o ServerAliveInterval=10 " +
|
58
|
+
"-o ServerAliveCountMax=6 -f -R #{@port}:localhost:#{@server.port} "
|
59
|
+
build_ssh_cmd(cmd, [cmds])
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.kill(name)
|
63
|
+
ssh = TasteTester::SSH.new(name)
|
64
|
+
# Since commands are &&'d together, and we're using &&, we need to
|
65
|
+
# surround this in paryns, and make sure as a whole it evaluates
|
66
|
+
# to true so it doesn't mess up other things... even though this is
|
67
|
+
# the only thing we're currently executing in this SSH.
|
68
|
+
if TasteTester::Config.windows_target
|
69
|
+
cmd = <<~EOPS
|
70
|
+
if (Test-Path "#{TasteTester::Config.timestamp_file}") {
|
71
|
+
$x = cat "#{TasteTester::Config.timestamp_file}"
|
72
|
+
if ($x -ne $null) {
|
73
|
+
kill -Force $x 2>$null
|
74
|
+
}
|
75
|
+
}
|
76
|
+
$LASTEXITCODE = 0
|
77
|
+
EOPS
|
78
|
+
else
|
79
|
+
cmd = "( [ -s #{TasteTester::Config.timestamp_file} ]" +
|
80
|
+
' && kill -9 -- ' +
|
81
|
+
"-\$(cat #{TasteTester::Config.timestamp_file}) 2>/dev/null; " +
|
82
|
+
' true )'
|
83
|
+
end
|
84
|
+
ssh << cmd
|
85
|
+
ssh.run!
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def windows_tunnel_cmd
|
91
|
+
# We are powershell. If you walk up you get:
|
92
|
+
# ppid - ssh
|
93
|
+
# pppid - ssh
|
94
|
+
# ppppid - ssh
|
95
|
+
# pppppid - services
|
96
|
+
#
|
97
|
+
# Unlike in Linux you don't need to walk up the tree, however. In fact,
|
98
|
+
# killing pppid or ppid didn't actually terminate the session. Only
|
99
|
+
# killing our actual powershell instance did.
|
100
|
+
#
|
101
|
+
# Moreover, it doesn't seem like re-parenting works the same way. So
|
102
|
+
# this is pretty simple.
|
103
|
+
#
|
104
|
+
# For the record, if you want to play with this, you do so with:
|
105
|
+
# (gwmi win32_process | ? processid -eq $PID).parentprocessid
|
106
|
+
#
|
107
|
+
# Also note that backtick is a line-continuation marker in powershell.
|
108
|
+
<<~EOS
|
109
|
+
$ts = "#{TasteTester::Config.timestamp_file}"
|
110
|
+
echo $PID | Out-File -Encoding ASCII "$ts"
|
111
|
+
# TODO: pull this from Host.touchcmd
|
112
|
+
(Get-Item "$ts").LastWriteTime=("#{TasteTester::Config.testing_end_time}")
|
41
113
|
|
114
|
+
while (1 -eq 1) {
|
115
|
+
if (-Not (Test-Path $ts)) {
|
116
|
+
# if we are here, we know we've created our source
|
117
|
+
Write-EventLog -LogName "Application" -Source "taste-tester" `
|
118
|
+
-EventID 5 -EntryType Information `
|
119
|
+
-Message "Ending tunnel: timestamp file disappeared"
|
120
|
+
}
|
121
|
+
sleep 60
|
122
|
+
}
|
123
|
+
done
|
124
|
+
EOS
|
125
|
+
end
|
126
|
+
|
127
|
+
def sane_os_tunnel_cmd
|
128
|
+
@ts = TasteTester::Config.testing_end_time.strftime('%y%m%d%H%M.%S')
|
42
129
|
# Tie the life of our SSH tunnel with the life of timestamp file.
|
43
130
|
# taste-testing can be renewed, so we'll wait until:
|
44
131
|
# 1. the timestamp file is entirely gone
|
45
132
|
# 2. our parent sshd process dies
|
46
133
|
# 3. new taste-tester instance is running (file contains different PGID)
|
47
|
-
|
134
|
+
<<~EOS
|
48
135
|
log() {
|
49
136
|
[ -e /usr/bin/logger ] || return
|
50
137
|
logger -t taste-tester "$*"
|
@@ -111,6 +198,7 @@ module TasteTester
|
|
111
198
|
SSH_PGID=$current_pgid
|
112
199
|
|
113
200
|
echo $SSH_PGID > #{TasteTester::Config.timestamp_file} && \
|
201
|
+
# TODO: pull this from Host.touchcmd
|
114
202
|
touch -t #{@ts} #{TasteTester::Config.timestamp_file} && \
|
115
203
|
while true; do
|
116
204
|
if ! [ -f "#{TasteTester::Config.timestamp_file}" ]; then
|
@@ -130,44 +218,6 @@ module TasteTester
|
|
130
218
|
sleep 60
|
131
219
|
done
|
132
220
|
EOS
|
133
|
-
# As great as it would be to have ExitOnForwardFailure=yes,
|
134
|
-
# we had multiple cases of tunnels dying
|
135
|
-
# if -f and ExitOnForwardFailure are used together.
|
136
|
-
# In most cases the first request from chef was "breaking" the tunnel,
|
137
|
-
# in a way that port was still open, but subsequent requests were hanging.
|
138
|
-
# This is reproducible and should be looked into.
|
139
|
-
jumps = TasteTester::Config.jumps ? "-J #{TasteTester::Config.jumps}" : ''
|
140
|
-
cmd = "#{TasteTester::Config.ssh_command} #{jumps} " +
|
141
|
-
"-o ConnectTimeout=#{TasteTester::Config.ssh_connect_timeout} " +
|
142
|
-
'-T -o BatchMode=yes ' +
|
143
|
-
'-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
|
144
|
-
'-o ServerAliveInterval=10 -o ServerAliveCountMax=6 ' +
|
145
|
-
"-f -R #{@port}:localhost:#{@server.port} "
|
146
|
-
cc = Base64.encode64(cmds).delete("\n")
|
147
|
-
|
148
|
-
# always base64 encode the command so we don't have to deal with quoting
|
149
|
-
cmd += "#{TasteTester::Config.user}@#{@host} \"echo '#{cc}' | base64" +
|
150
|
-
' --decode'
|
151
|
-
if TasteTester::Config.user != 'root'
|
152
|
-
cmd += ' | sudo bash -x"'
|
153
|
-
else
|
154
|
-
cmd += ' | bash -x"'
|
155
|
-
end
|
156
|
-
cmd
|
157
|
-
end
|
158
|
-
|
159
|
-
def self.kill(name)
|
160
|
-
ssh = TasteTester::SSH.new(name)
|
161
|
-
# Since commands are &&'d together, and we're using &&, we need to
|
162
|
-
# surround this in paryns, and make sure as a whole it evaluates
|
163
|
-
# to true so it doesn't mess up other things... even though this is
|
164
|
-
# the only thing we're currently executing in this SSH.
|
165
|
-
cmd = "( [ -s #{TasteTester::Config.timestamp_file} ]" +
|
166
|
-
' && kill -9 -- ' +
|
167
|
-
"-\$(cat #{TasteTester::Config.timestamp_file}) 2>/dev/null; " +
|
168
|
-
' true )'
|
169
|
-
ssh << cmd
|
170
|
-
ssh.run!
|
171
221
|
end
|
172
222
|
end
|
173
223
|
end
|
data/scripts/taste-untester
CHANGED
@@ -14,19 +14,19 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
-
|
18
|
-
CONFIG_NAME='client'
|
19
|
-
|
20
|
-
CONFIG_FILE="${CONFIG_FILE:-/etc/taste-untester-config}"
|
21
|
-
if [ -e "$CONFIG_FILE" ]; then
|
22
|
-
source "$COFNIG_FILE"
|
23
|
-
fi
|
24
|
-
|
17
|
+
# default configs
|
25
18
|
CONFLINK='/etc/chef/client.rb'
|
26
19
|
PRODCONF='/etc/chef/client-prod.rb'
|
27
20
|
CERTLINK='/etc/chef/client.pem'
|
28
21
|
PRODCERT='/etc/chef/client-prod.pem'
|
29
22
|
STAMPFILE='/etc/chef/test_timestamp'
|
23
|
+
|
24
|
+
# let the config file overwrite them
|
25
|
+
CONFIG_FILE="${CONFIG_FILE:-/etc/taste-untester-config}"
|
26
|
+
if [ -e "$CONFIG_FILE" ]; then
|
27
|
+
source "$CONFIG_FILE"
|
28
|
+
fi
|
29
|
+
|
30
30
|
MYSELF=$0
|
31
31
|
DRYRUN=0
|
32
32
|
DEBUG=0
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Copyright 2020-present Facebook
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
[CmdletBinding()]
|
16
|
+
param(
|
17
|
+
[switch]$dryrun
|
18
|
+
)
|
19
|
+
|
20
|
+
# keep these as *forward* slashes
|
21
|
+
$CONFLINK = 'C:/chef/client.rb'
|
22
|
+
$PRODCONF = 'C:/chef/client-prod.rb'
|
23
|
+
$CERTLINK = 'C:/chef/client.pem'
|
24
|
+
$PRODCERT = 'C:/chef/client-prod.pem'
|
25
|
+
$STAMPFILE = 'C:/chef/test_timestamp'
|
26
|
+
$MYSELF = $0
|
27
|
+
|
28
|
+
function log($msg) {
|
29
|
+
Write-EventLog -LogName "Application" -Source "taste-tester" `
|
30
|
+
-EventID 2 -EntryType Warning -Message $msg
|
31
|
+
}
|
32
|
+
|
33
|
+
function set_server_to_prod {
|
34
|
+
if (Test-Path $STAMPFILE) {
|
35
|
+
$content = Get-Content $STAMPFILE
|
36
|
+
if ($content -ne $null) {
|
37
|
+
kill $content -Force 2>$null
|
38
|
+
}
|
39
|
+
}
|
40
|
+
rm -Force $CONFLINK
|
41
|
+
New-Item -ItemType symboliclink -Force -Value $PRODCONF $CONFLINK
|
42
|
+
if (Test-Path $STAMPFILE) {
|
43
|
+
rm -Force $STAMPFILE
|
44
|
+
}
|
45
|
+
log "Reverted to production Chef."
|
46
|
+
}
|
47
|
+
|
48
|
+
function check_server {
|
49
|
+
# this is the only way to check if something is a symlink, apparently
|
50
|
+
if (-Not ((get-item $CONFLINK).Attributes.ToString() -match "ReparsePoint")) {
|
51
|
+
Write-Verbose "$CONFLINK is not a link..."
|
52
|
+
return
|
53
|
+
}
|
54
|
+
$current_config = (Get-Item $CONFLINK).target
|
55
|
+
if ($current_config -eq $PRODCONF) {
|
56
|
+
if (Test-Path $STAMPFILE) {
|
57
|
+
rm -Force $STAMPFILE
|
58
|
+
}
|
59
|
+
return
|
60
|
+
}
|
61
|
+
|
62
|
+
$revert = $false
|
63
|
+
if (-Not (Test-Path $STAMPFILE)) {
|
64
|
+
$revert = $true
|
65
|
+
} else {
|
66
|
+
$now = [int][double]::Parse(
|
67
|
+
$(Get-Date -date (Get-Date).ToUniversalTime()-uformat %s)
|
68
|
+
)
|
69
|
+
$stamp_time = Get-Date -Date `
|
70
|
+
(Get-Item $STAMPFILE).LastWriteTime.ToUniversalTime() -UFormat %s
|
71
|
+
Write-Verbose "$now vs $stamp_time"
|
72
|
+
if ($now -gt $stamp_time) {
|
73
|
+
$revert = $true
|
74
|
+
}
|
75
|
+
}
|
76
|
+
if ($revert) {
|
77
|
+
if ($dryrun) {
|
78
|
+
echo "DRYRUN: Would return server to prod"
|
79
|
+
} else {
|
80
|
+
set_server_to_prod
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
check_server
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taste_tester
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Phil Dibowitz
|
8
8
|
- Marcin Sawicki
|
9
9
|
autorequire:
|
10
|
-
bindir:
|
10
|
+
bindir:
|
11
|
+
- bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2020-
|
13
|
+
date: 2020-08-24 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: between_meals
|
@@ -17,30 +18,16 @@ dependencies:
|
|
17
18
|
requirements:
|
18
19
|
- - ">="
|
19
20
|
- !ruby/object:Gem::Version
|
20
|
-
version: 0.0.
|
21
|
+
version: 0.0.11
|
21
22
|
type: :runtime
|
22
23
|
prerelease: false
|
23
24
|
version_requirements: !ruby/object:Gem::Requirement
|
24
25
|
requirements:
|
25
26
|
- - ">="
|
26
27
|
- !ruby/object:Gem::Version
|
27
|
-
version: 0.0.
|
28
|
+
version: 0.0.11
|
28
29
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - ">="
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: 2.0.0
|
35
|
-
type: :runtime
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ">="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: 2.0.0
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: mixlib-config
|
30
|
+
name: chef
|
44
31
|
requirement: !ruby/object:Gem::Requirement
|
45
32
|
requirements:
|
46
33
|
- - ">="
|
@@ -68,47 +55,19 @@ dependencies:
|
|
68
55
|
- !ruby/object:Gem::Version
|
69
56
|
version: '0'
|
70
57
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
58
|
+
name: json
|
72
59
|
requirement: !ruby/object:Gem::Requirement
|
73
60
|
requirements:
|
74
61
|
- - ">="
|
75
62
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
63
|
+
version: 2.0.0
|
77
64
|
type: :runtime
|
78
65
|
prerelease: false
|
79
66
|
version_requirements: !ruby/object:Gem::Requirement
|
80
67
|
requirements:
|
81
68
|
- - ">="
|
82
69
|
- !ruby/object:Gem::Version
|
83
|
-
version:
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: chef-zero
|
86
|
-
requirement: !ruby/object:Gem::Requirement
|
87
|
-
requirements:
|
88
|
-
- - ">="
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: '0'
|
91
|
-
type: :development
|
92
|
-
prerelease: false
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
94
|
-
requirements:
|
95
|
-
- - ">="
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: '0'
|
98
|
-
- !ruby/object:Gem::Dependency
|
99
|
-
name: knife-solo
|
100
|
-
requirement: !ruby/object:Gem::Requirement
|
101
|
-
requirements:
|
102
|
-
- - ">="
|
103
|
-
- !ruby/object:Gem::Version
|
104
|
-
version: '0'
|
105
|
-
type: :development
|
106
|
-
prerelease: false
|
107
|
-
version_requirements: !ruby/object:Gem::Requirement
|
108
|
-
requirements:
|
109
|
-
- - ">="
|
110
|
-
- !ruby/object:Gem::Version
|
111
|
-
version: '0'
|
70
|
+
version: 2.0.0
|
112
71
|
- !ruby/object:Gem::Dependency
|
113
72
|
name: minitar
|
114
73
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,19 +83,19 @@ dependencies:
|
|
124
83
|
- !ruby/object:Gem::Version
|
125
84
|
version: 0.6.1
|
126
85
|
- !ruby/object:Gem::Dependency
|
127
|
-
name:
|
86
|
+
name: mixlib-config
|
128
87
|
requirement: !ruby/object:Gem::Requirement
|
129
88
|
requirements:
|
130
|
-
- -
|
89
|
+
- - ">="
|
131
90
|
- !ruby/object:Gem::Version
|
132
|
-
version: 0
|
133
|
-
type: :
|
91
|
+
version: '0'
|
92
|
+
type: :runtime
|
134
93
|
prerelease: false
|
135
94
|
version_requirements: !ruby/object:Gem::Requirement
|
136
95
|
requirements:
|
137
|
-
- -
|
96
|
+
- - ">="
|
138
97
|
- !ruby/object:Gem::Version
|
139
|
-
version: 0
|
98
|
+
version: '0'
|
140
99
|
description: Utility for testing Chef changes using chef-zero
|
141
100
|
email:
|
142
101
|
executables:
|
@@ -160,10 +119,12 @@ files:
|
|
160
119
|
- lib/taste_tester/noop.rb
|
161
120
|
- lib/taste_tester/server.rb
|
162
121
|
- lib/taste_tester/ssh.rb
|
122
|
+
- lib/taste_tester/ssh_util.rb
|
163
123
|
- lib/taste_tester/state.rb
|
164
124
|
- lib/taste_tester/tunnel.rb
|
165
125
|
- lib/taste_tester/windows.rb
|
166
126
|
- scripts/taste-untester
|
127
|
+
- scripts/taste-untester.ps1
|
167
128
|
homepage: https://github.com/facebook/taste-tester
|
168
129
|
licenses:
|
169
130
|
- Apache-2.0
|
@@ -183,8 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
144
|
- !ruby/object:Gem::Version
|
184
145
|
version: '0'
|
185
146
|
requirements: []
|
186
|
-
|
187
|
-
rubygems_version: 2.7.6.2
|
147
|
+
rubygems_version: 3.1.2
|
188
148
|
signing_key:
|
189
149
|
specification_version: 4
|
190
150
|
summary: Taste Tester
|