taste_tester 0.0.12 → 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.
@@ -17,6 +17,7 @@
17
17
  require 'fileutils'
18
18
  require 'socket'
19
19
  require 'timeout'
20
+ require 'chef/mash'
20
21
 
21
22
  require 'between_meals/util'
22
23
  require 'taste_tester/config'
@@ -29,13 +30,11 @@ module TasteTester
29
30
  include ::BetweenMeals::Util
30
31
 
31
32
  def initialize
32
- ref_dir = File.dirname(File.expand_path(
33
- TasteTester::Config.ref_file,
34
- ))
33
+ ref_dir = File.dirname(File.expand_path(TasteTester::Config.ref_file))
35
34
  unless File.directory?(ref_dir)
36
35
  begin
37
36
  FileUtils.mkpath(ref_dir)
38
- rescue => e
37
+ rescue StandardError => e
39
38
  logger.error("Chef temp dir #{ref_dir} missing and can't be created")
40
39
  logger.error(e)
41
40
  exit(1)
@@ -83,6 +82,25 @@ module TasteTester
83
82
  write(:ref, ref)
84
83
  end
85
84
 
85
+ def last_upload_time
86
+ TasteTester::State.read(:last_upload_time)
87
+ end
88
+
89
+ def last_upload_time=(time)
90
+ write(:last_upload_time, time)
91
+ end
92
+
93
+ def bundle
94
+ val = TasteTester::State.read(:bundle)
95
+ # promote value to symbol to match config value.
96
+ return :compatible if val == 'compatible'
97
+ val
98
+ end
99
+
100
+ def bundle=(bundle)
101
+ write(:bundle, bundle)
102
+ end
103
+
86
104
  def update(vals)
87
105
  merge(vals)
88
106
  end
@@ -101,14 +119,14 @@ module TasteTester
101
119
 
102
120
  def real_wipe
103
121
  if TasteTester::Config.ref_file &&
104
- File.exists?(TasteTester::Config.ref_file)
122
+ File.exist?(TasteTester::Config.ref_file)
105
123
  File.delete(TasteTester::Config.ref_file)
106
124
  end
107
125
  end
108
126
 
109
127
  def self.read(key)
110
128
  JSON.parse(File.read(TasteTester::Config.ref_file))[key.to_s]
111
- rescue => e
129
+ rescue StandardError => e
112
130
  logger.debug(e)
113
131
  nil
114
132
  end
@@ -120,9 +138,13 @@ module TasteTester
120
138
  end
121
139
 
122
140
  def merge(vals)
141
+ # we generally use symbols for the keys, but to/from JSON will
142
+ # give us strings, and thus duplicate keys, which is bad. So
143
+ # use a Mash
144
+ state = Mash.new
123
145
  begin
124
146
  state = JSON.parse(File.read(TasteTester::Config.ref_file))
125
- rescue
147
+ rescue StandardError
126
148
  state = {}
127
149
  end
128
150
  state.merge!(vals)
@@ -132,7 +154,7 @@ module TasteTester
132
154
  )
133
155
  ff.write(state.to_json)
134
156
  ff.close
135
- rescue => e
157
+ rescue StandardError => e
136
158
  logger.error('Unable to write the reffile')
137
159
  logger.debug(e)
138
160
  exit 0
@@ -14,64 +14,49 @@
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
 
25
- def initialize(host, server, timeout = 5)
30
+ def initialize(host, server)
26
31
  @host = host
27
32
  @server = server
28
- @timeout = timeout
29
- if TasteTester::Config.testing_until
30
- @delta_secs = TasteTester::Config.testing_until.strftime('%s').to_i -
31
- Time.now.strftime('%s').to_i
32
- else
33
- @delta_secs = TasteTester::Config.testing_time
34
- end
35
33
  end
36
34
 
37
35
  def run
38
36
  @port = TasteTester::Config.tunnel_port
39
37
  logger.info("Setting up tunnel on port #{@port}")
40
- @status, @output = exec!(cmd, logger)
41
- rescue
42
- logger.error 'Failed bringing up ssh tunnel'
43
- exit(1)
38
+ exec!(cmd, logger)
39
+ rescue StandardError => e
40
+ logger.error "Failed bringing up ssh tunnel: #{e}"
41
+ error!
44
42
  end
45
43
 
46
44
  def cmd
47
- if TasteTester::Config.user != 'root'
48
- pid = '$$'
45
+ if TasteTester::Config.windows_target
46
+ cmds = windows_tunnel_cmd
49
47
  else
50
- pid = '\\$\\$'
48
+ cmds = sane_os_tunnel_cmd
51
49
  end
52
- cmds = "ps -p #{pid} -o pgid | grep -v PGID" +
53
- " > #{TasteTester::Config.timestamp_file} &&" +
54
- " touch -t #{TasteTester::Config.testing_end_time}" +
55
- " #{TasteTester::Config.timestamp_file} && sleep #{@delta_secs}"
50
+
56
51
  # As great as it would be to have ExitOnForwardFailure=yes,
57
52
  # we had multiple cases of tunnels dying
58
53
  # if -f and ExitOnForwardFailure are used together.
59
54
  # In most cases the first request from chef was "breaking" the tunnel,
60
55
  # in a way that port was still open, but subsequent requests were hanging.
61
56
  # This is reproducible and should be looked into.
62
- cmd = "#{TasteTester::Config.ssh_command} " +
63
- "-T -o BatchMode=yes -o ConnectTimeout=#{@timeout} " +
64
- '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
65
- '-o ServerAliveInterval=10 -o ServerAliveCountMax=6 ' +
66
- "-f -R #{@port}:localhost:#{@server.port} "
67
- if TasteTester::Config.user != 'root'
68
- cc = Base64.encode64(cmds).delete("\n")
69
- cmd += "#{TasteTester::Config.user}@#{@host} \"echo '#{cc}' | base64" +
70
- ' --decode | sudo bash -x"'
71
- else
72
- cmd += "root@#{@host} \"#{cmds}\""
73
- end
74
- cmd
57
+ cmd = "#{ssh_base_cmd} -o ServerAliveInterval=10 " +
58
+ "-o ServerAliveCountMax=6 -f -R #{@port}:localhost:#{@server.port} "
59
+ build_ssh_cmd(cmd, [cmds])
75
60
  end
76
61
 
77
62
  def self.kill(name)
@@ -80,14 +65,159 @@ module TasteTester
80
65
  # surround this in paryns, and make sure as a whole it evaluates
81
66
  # to true so it doesn't mess up other things... even though this is
82
67
  # the only thing we're currently executing in this SSH.
83
- if TasteTester::Config.user != 'root'
84
- sudo = 'sudo '
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 )'
85
83
  end
86
- cmd = "( [ -s #{TasteTester::Config.timestamp_file} ]" +
87
- " && #{sudo}kill -9 -- " +
88
- "-\$(cat #{TasteTester::Config.timestamp_file}); true )"
89
84
  ssh << cmd
90
85
  ssh.run!
91
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}")
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')
129
+ # Tie the life of our SSH tunnel with the life of timestamp file.
130
+ # taste-testing can be renewed, so we'll wait until:
131
+ # 1. the timestamp file is entirely gone
132
+ # 2. our parent sshd process dies
133
+ # 3. new taste-tester instance is running (file contains different PGID)
134
+ <<~EOS
135
+ log() {
136
+ [ -e /usr/bin/logger ] || return
137
+ logger -t taste-tester "$*"
138
+ }
139
+ # sets $current_pgid
140
+ # This is important, this should just be called ald let it set the
141
+ # variable. Do NOT call in a subshell like foo=$(get_current_pgid)
142
+ # as then you end up even further down the list of children
143
+ get_current_pgid() {
144
+
145
+ # if TT user is non-root, then it breaks down like this:
146
+ # we are 'bash'
147
+ # our parent is 'sudo'
148
+ # our parent's parent is 'bash "echo ..." | sudo bash -x'
149
+ # our parent's parent's parent is ssh
150
+ # - we want the progress-group ID of *that*
151
+ #
152
+ # EXCEPT... sometimes sudo forks itself one more time so it's
153
+ # we are 'bash'
154
+ # our parent is 'sudo'
155
+ # our parent's parent 'sudo'
156
+ # our parent's parent's parent is 'bash "echo ..." | sudo bash -x'
157
+ # our parent's parent's parent's parent is ssh
158
+ # - we want the progress-group ID of *that*
159
+ #
160
+ # BUT if the TT user is root, no sudo at all...
161
+ # we are 'bash'
162
+ # our parent is 'bash "echo ..." | bash -c
163
+ # our parent's parent is ssh
164
+ # - we want the progress-group ID of *that*
165
+ #
166
+ # We can make all sorts of assumptions, but the most reliable way
167
+ # to do this that's always correct is to is simply to walk parents until
168
+ # we hit something with SSH in the name. Start with PPID and go from
169
+ # there.
170
+ #
171
+ # There's a few commented out 'log's here that are too verbose
172
+ # for operation (since this function runs every minute) but are useful
173
+ # for debugging.
174
+
175
+ relevant_pid=''
176
+ current_pid=$PPID
177
+ while true; do
178
+ name=$(ps -o command= -p $current_pid)
179
+ if [[ "$name" =~ sshd ]]; then
180
+ # Uncomment the following for debugging...
181
+ #log "$current_pid is ssh, that's us!"
182
+ relevant_pid=$current_pid
183
+ break
184
+ fi
185
+ # Uncomment the following for debugging...
186
+ #log "$current_pid is $name, finding parent..."
187
+ current_pid=$(ps -o ppid= -p $current_pid)
188
+ done
189
+ if [ -z "$relevant_pid" ];then
190
+ log "Cannot determine relevant PGID"
191
+ exit 42
192
+ fi
193
+ current_pgid="$(ps -o pgid= -p $relevant_pid | sed "s| ||g")"
194
+ # Uncomment the following for debugging...
195
+ #log "PGID of ssh ($relevant_pid) is $current_pgid"
196
+ }
197
+ get_current_pgid
198
+ SSH_PGID=$current_pgid
199
+
200
+ echo $SSH_PGID > #{TasteTester::Config.timestamp_file} && \
201
+ # TODO: pull this from Host.touchcmd
202
+ touch -t #{@ts} #{TasteTester::Config.timestamp_file} && \
203
+ while true; do
204
+ if ! [ -f "#{TasteTester::Config.timestamp_file}" ]; then
205
+ log "Ending tunnel: timestamp file disappeared"
206
+ break
207
+ fi
208
+ current_pid="$(cat #{TasteTester::Config.timestamp_file})"
209
+ if ! [ "$current_pid" = "$SSH_PGID" ]; then
210
+ log "Ending tunnel: timestamp PGID changed"
211
+ break
212
+ fi
213
+ get_current_pgid
214
+ if ! [ "$current_pgid" = "$SSH_PGID" ]; then
215
+ log "Ending tunnel: timestamp PGID isn't ours"
216
+ break
217
+ fi
218
+ sleep 60
219
+ done
220
+ EOS
221
+ end
92
222
  end
93
223
  end
@@ -40,6 +40,7 @@ module TasteTester
40
40
  cmd << " --log-file #{@log_file} --log-level debug"
41
41
  end
42
42
  cmd << ' --ssl' if TasteTester::Config.use_ssl
43
+ cmd << " --file-store #{@fsroot}" if TasteTester::Config.bundle
43
44
 
44
45
  # Mixlib::Shellout will always wait for a process to finish before
45
46
  # returning, so we use `spawn` instead.
@@ -14,11 +14,19 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
+ # default configs
17
18
  CONFLINK='/etc/chef/client.rb'
18
19
  PRODCONF='/etc/chef/client-prod.rb'
19
20
  CERTLINK='/etc/chef/client.pem'
20
21
  PRODCERT='/etc/chef/client-prod.pem'
21
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
+
22
30
  MYSELF=$0
23
31
  DRYRUN=0
24
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