taste_tester 0.0.16 → 0.0.17
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 +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
|
-
[](https://circleci.com/gh/facebook/taste-tester)
|
3
|
+

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