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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 945fe28beeb0251fc2b6236c833e77b8b0f6e7b2242c9f7d4882bd4fcf270506
4
- data.tar.gz: 1001f712736d466b1fe1db53332d7668ebebf028d62a8566c2923ee88db2f0d4
3
+ metadata.gz: 01cc261c810b1efa71e7eba767be42c7292a2e7ebfa1f0ad2a81cb0aab465504
4
+ data.tar.gz: 2891b4d9a14b03183b24dc2fd3baada23b3a86e0ba741eb2ff43ed4e9bb15ef0
5
5
  SHA512:
6
- metadata.gz: ccf12a4962ae584ff10f2b0b7782a48d2e9e04f320a0b592895b3ec545a2921372ccd4e5e9b8994435791fbe281fe9d9043881b2f780f6aadd60ec64613e910f
7
- data.tar.gz: 5b8f422febc1b1f8d2bee6a408aaff95a1dbd41011ad267c4637180a53214219fc44d725543160866ea8947bacd5eec68e002d6afc87352f7fa721fed05157ea
6
+ metadata.gz: aa9dee7d09f5a0ffd03c63d2bd96cabde536b498fb8f4efeab8fbfd564ecb21166954200f8d62702833b3c7977aba0b979e920e93d047a27e9ea4d5538d88dbf
7
+ data.tar.gz: 593d8b054ff209d1c5ed116d90a00bca9bb2a64a3d8e2c576e068e91321d286a3e2b30347179d22dafc09ca4525c70c2d49351e8b838fa3c8063ed5a006a89e4
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Taste Tester
2
2
 
3
- [![TravisCI](https://travis-ci.org/facebook/taste-tester.svg)](http://travis-ci.org/facebook/taste-tester)
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
- **Plugin example**
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
 
@@ -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.exists?(File.expand_path(TasteTester::Config.config_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.exists?(File.expand_path(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.exists?(File.expand_path(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.exists?(File.expand_path(options[:config_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.exists?(path)
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
- include TasteTester
470
+ module TasteTester
471
+ include TasteTester
472
+ end
463
473
  end
@@ -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
- Find.find('.') do |p|
140
- # ignore current directory. The File.directory? would also skip it,
141
- # but we need to do it early because the string is too short for the
142
- # next statement.
143
- next if p == '.'
144
- # paths are enumerated as relative to the input path '.', so we get
145
- # './dir/file'. Stripping off the first two characters gives us a
146
- # a cleaner 'dir/file' path.
147
- relative_name = p[2..-1]
148
- # some exclusions are rooted relative to cookbooks which are one
149
- # level below the directory included here. Strip off the first part
150
- # of the path. e.g. `fb_cookbook/spec/foo.rb` would be `spec/foo.rb`
151
- # which matches `spec/*`.
152
- relative_name_minus_first = relative_name.split(
153
- File::SEPARATOR,
154
- )[1..-1].join(File::SEPARATOR)
155
- next if chefignores.ignored?(relative_name) ||
156
- chefignores.ignored?(relative_name_minus_first)
157
- name = File.join(destination, relative_name)
158
- if File.directory?(p)
159
- # skip it. This also handles symlinks to directories which aren't
160
- # useful either.
161
- elsif File.symlink?(p)
162
- # tar handling of filenames > 100 characters gets complex. We'd use
163
- # split_name from Minitar, but it's a private method. It's
164
- # reasonable to assume that all symlink names in the bundle are
165
- # less than 100 characters long. Long term, the version of minitar
166
- # in chefdk should be upgraded.
167
- fail 'Add support for long symlink paths' if name.size > 100
168
- # The version of Minitar included in chefdk does not support
169
- # symlinks directly. Therefore we use direct writes to the
170
- # underlying stream to reproduce the symlinks
171
- symlink = {
172
- :name => name,
173
- :mode => 0644,
174
- :typeflag => '2',
175
- :size => 0,
176
- :linkname => File.readlink(p),
177
- :prefix => '',
178
- }
179
- stream.write(Minitar::PosixHeader.new(symlink))
180
- else
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
@@ -63,6 +63,7 @@ module TasteTester
63
63
  no_repo false
64
64
  json false
65
65
  jumps nil
66
+ windows_target false
66
67
 
67
68
  # Start/End refs for calculating changes in the repo.
68
69
  # - start_ref should be the "master" commit of the repository
@@ -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.exists?(path)
69
+ unless File.exist?(path)
70
70
  logger.error('Plugin file not found')
71
71
  exit(1)
72
72
  end
@@ -92,20 +92,11 @@ module TasteTester
92
92
  # see if someone else is taste-testing
93
93
  transport << we_testing
94
94
 
95
- transport << 'logger -t taste-tester Moving server into taste-tester' +
96
- " for #{@user}"
97
- transport << touchcmd
98
- # shell redirection is also racy, so make a temporary file first
99
- transport << "tmpconf=$(mktemp #{TasteTester::Config.chef_config_path}/" +
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
- config_prod = TasteTester::Config.chef_config.split('.').join('-prod.')
146
- [
147
- "ln -vsf #{TasteTester::Config.chef_config_path}/#{config_prod} " +
148
- "#{TasteTester::Config.chef_config_path}/" +
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
- shellcode = <<~ENDOFSHELLCODE
172
- awk "\\$0 ~ /^#{USER_PREAMBLE}/{
173
- if (\\$NF != \\"#{@user}\\"){
174
- print \\$NF;
175
- exit 42
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
- }" #{config_file}
178
- ENDOFSHELLCODE
179
- shellcode.delete("\n")
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
- touch = Base64.encode64(
200
- "if [ 'Darwin' = $(uname) ]; then touch -t \"$(date -r " +
201
- "#{TasteTester::Config.testing_end_time.to_i} +'%Y%m%d%H%M.%S')\" " +
202
- "#{TasteTester::Config.timestamp_file}; else touch --date \"$(date " +
203
- "-d @#{TasteTester::Config.testing_end_time.to_i} +'%Y-%m-%d %T')\" " +
204
- "#{TasteTester::Config.timestamp_file}; fi",
205
- ).delete("\n")
206
- "/bin/echo -n '#{touch}' | base64 --decode | bash"
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
- return ttconfig
422
+ ttconfig
299
423
  end
300
424
  end
301
425
  end
@@ -161,7 +161,7 @@ module TasteTester
161
161
  end
162
162
 
163
163
  def start_chef_zero
164
- File.unlink(@log_file) if File.exists?(@log_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,
@@ -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
- cmds = @cmds.join(' && ')
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
@@ -119,7 +119,7 @@ module TasteTester
119
119
 
120
120
  def real_wipe
121
121
  if TasteTester::Config.ref_file &&
122
- File.exists?(TasteTester::Config.ref_file)
122
+ File.exist?(TasteTester::Config.ref_file)
123
123
  File.delete(TasteTester::Config.ref_file)
124
124
  end
125
125
  end
@@ -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
- exit(1)
41
+ error!
37
42
  end
38
43
 
39
44
  def cmd
40
- @ts = TasteTester::Config.testing_end_time.strftime('%y%m%d%H%M.%S')
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
- cmds = <<~EOS
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
@@ -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
- CONFIG_PATH='/etc/chef'
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.16
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: bin
10
+ bindir:
11
+ - bin
11
12
  cert_chain: []
12
- date: 2020-05-22 00:00:00.000000000 Z
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.10
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.10
28
+ version: 0.0.11
28
29
  - !ruby/object:Gem::Dependency
29
- name: json
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: chef
58
+ name: json
72
59
  requirement: !ruby/object:Gem::Requirement
73
60
  requirements:
74
61
  - - ">="
75
62
  - !ruby/object:Gem::Version
76
- version: '0'
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: '0'
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: rubocop
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.49.1
133
- type: :development
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.49.1
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
- rubyforge_project:
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