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