taste_tester 0.0.14 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +33 -8
- data/bin/taste-tester +31 -9
- data/lib/taste_tester/client.rb +49 -40
- data/lib/taste_tester/commands.rb +14 -11
- data/lib/taste_tester/config.rb +2 -0
- data/lib/taste_tester/hooks.rb +7 -7
- data/lib/taste_tester/host.rb +181 -66
- data/lib/taste_tester/locallink.rb +2 -4
- data/lib/taste_tester/noop.rb +1 -3
- data/lib/taste_tester/server.rb +5 -5
- data/lib/taste_tester/ssh.rb +7 -30
- data/lib/taste_tester/ssh_util.rb +127 -0
- data/lib/taste_tester/state.rb +5 -2
- data/lib/taste_tester/tunnel.rb +172 -35
- data/scripts/taste-untester +8 -8
- data/scripts/taste-untester.ps1 +85 -0
- metadata +21 -47
@@ -22,8 +22,6 @@ module TasteTester
|
|
22
22
|
include TasteTester::Logging
|
23
23
|
include BetweenMeals::Util
|
24
24
|
|
25
|
-
attr_reader :output, :status
|
26
|
-
|
27
25
|
def initialize
|
28
26
|
@host = 'localhost'
|
29
27
|
@cmds = []
|
@@ -36,11 +34,11 @@ module TasteTester
|
|
36
34
|
alias << add
|
37
35
|
|
38
36
|
def run
|
39
|
-
|
37
|
+
exec(cmd, logger)
|
40
38
|
end
|
41
39
|
|
42
40
|
def run!
|
43
|
-
|
41
|
+
exec!(cmd, logger)
|
44
42
|
rescue StandardError => e
|
45
43
|
logger.error(e.message)
|
46
44
|
error!
|
data/lib/taste_tester/noop.rb
CHANGED
@@ -22,8 +22,6 @@ module TasteTester
|
|
22
22
|
include TasteTester::Logging
|
23
23
|
include BetweenMeals::Util
|
24
24
|
|
25
|
-
attr_reader :output, :status
|
26
|
-
|
27
25
|
def initialize
|
28
26
|
print_noop_warning
|
29
27
|
@host = 'localhost'
|
@@ -48,7 +46,7 @@ module TasteTester
|
|
48
46
|
alias << add
|
49
47
|
|
50
48
|
def run
|
51
|
-
|
49
|
+
run!
|
52
50
|
end
|
53
51
|
|
54
52
|
def run!
|
data/lib/taste_tester/server.rb
CHANGED
@@ -16,7 +16,6 @@
|
|
16
16
|
|
17
17
|
require 'fileutils'
|
18
18
|
require 'socket'
|
19
|
-
require 'timeout'
|
20
19
|
|
21
20
|
require 'between_meals/util'
|
22
21
|
require 'taste_tester/config'
|
@@ -65,10 +64,10 @@ module TasteTester
|
|
65
64
|
# on all addresses - both v4 and v6. Note that on localhost, ::1 is
|
66
65
|
# v6-only, so we default to 127.0.0.1 instead.
|
67
66
|
if TasteTester::Config.use_ssh_tunnels
|
68
|
-
@
|
67
|
+
@addrs = ['127.0.0.1']
|
69
68
|
@host = 'localhost'
|
70
69
|
else
|
71
|
-
@
|
70
|
+
@addrs = ['::', '0.0.0.0']
|
72
71
|
begin
|
73
72
|
@host = TasteTester::Config.my_hostname || Socket.gethostname
|
74
73
|
rescue StandardError
|
@@ -162,7 +161,7 @@ module TasteTester
|
|
162
161
|
end
|
163
162
|
|
164
163
|
def start_chef_zero
|
165
|
-
File.unlink(@log_file) if File.
|
164
|
+
File.unlink(@log_file) if File.exist?(@log_file)
|
166
165
|
@state.update({
|
167
166
|
:port => TasteTester::Config.chef_port,
|
168
167
|
:ssl => TasteTester::Config.use_ssl,
|
@@ -175,7 +174,8 @@ module TasteTester
|
|
175
174
|
extend ::TasteTester::Windows
|
176
175
|
start_win_chef_zero_server
|
177
176
|
else
|
178
|
-
|
177
|
+
hostarg = @addrs.map { |addr| "--host #{addr}" }.join(' ')
|
178
|
+
cmd = +"#{chef_zero_path} #{hostarg} --port #{@state.port} -d"
|
179
179
|
if TasteTester::Config.chef_zero_logging
|
180
180
|
cmd << " --log-file #{@log_file}" +
|
181
181
|
' --log-level debug'
|
data/lib/taste_tester/ssh.rb
CHANGED
@@ -15,14 +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
|
24
|
-
|
25
|
-
attr_reader :output, :status
|
25
|
+
include TasteTester::SSH::Util
|
26
26
|
|
27
27
|
def initialize(host, tunnel = false)
|
28
28
|
@host = host
|
@@ -36,47 +36,24 @@ module TasteTester
|
|
36
36
|
|
37
37
|
alias << add
|
38
38
|
|
39
|
-
def run
|
40
|
-
|
39
|
+
def run(stream = nil)
|
40
|
+
exec(cmd, logger, stream)
|
41
41
|
end
|
42
42
|
|
43
|
-
def run!
|
44
|
-
|
43
|
+
def run!(stream = nil)
|
44
|
+
exec!(cmd, logger, stream)
|
45
45
|
rescue StandardError => e
|
46
46
|
logger.error(e.message)
|
47
47
|
error!
|
48
48
|
end
|
49
49
|
|
50
|
-
def error!
|
51
|
-
error = <<-ERRORMESSAGE
|
52
|
-
SSH returned error while connecting to #{TasteTester::Config.user}@#{@host}
|
53
|
-
The host might be broken or your SSH access is not working properly
|
54
|
-
Try doing
|
55
|
-
#{TasteTester::Config.ssh_command} -v #{TasteTester::Config.user}@#{@host}
|
56
|
-
and come back once that works
|
57
|
-
ERRORMESSAGE
|
58
|
-
error.lines.each { |x| logger.error x.strip }
|
59
|
-
fail TasteTester::Exceptions::SshError
|
60
|
-
end
|
61
|
-
|
62
50
|
private
|
63
51
|
|
64
52
|
def cmd
|
65
53
|
@cmds.each do |cmd|
|
66
54
|
logger.info("Will run: '#{cmd}' on #{@host}")
|
67
55
|
end
|
68
|
-
|
69
|
-
cmd = "#{TasteTester::Config.ssh_command} " +
|
70
|
-
'-T -o BatchMode=yes ' +
|
71
|
-
"-o ConnectTimeout=#{TasteTester::Config.ssh_connect_timeout} " +
|
72
|
-
"#{TasteTester::Config.user}@#{@host} "
|
73
|
-
if TasteTester::Config.user != 'root'
|
74
|
-
cc = Base64.encode64(cmds).delete("\n")
|
75
|
-
cmd += "\"echo '#{cc}' | base64 --decode | sudo bash -x\""
|
76
|
-
else
|
77
|
-
cmd += "\'#{cmds}\'"
|
78
|
-
end
|
79
|
-
cmd
|
56
|
+
build_ssh_cmd(ssh_base_cmd, @cmds)
|
80
57
|
end
|
81
58
|
end
|
82
59
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module TasteTester
|
2
|
+
class SSH
|
3
|
+
module Util
|
4
|
+
def ssh_base_cmd
|
5
|
+
jumps = TasteTester::Config.jumps ?
|
6
|
+
"-J #{TasteTester::Config.jumps}" : ''
|
7
|
+
"#{TasteTester::Config.ssh_command} #{jumps} -T -o BatchMode=yes " +
|
8
|
+
'-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
|
9
|
+
"-o ConnectTimeout=#{TasteTester::Config.ssh_connect_timeout} " +
|
10
|
+
"#{TasteTester::Config.user}@#{@host} "
|
11
|
+
end
|
12
|
+
|
13
|
+
def error!
|
14
|
+
error = <<~ERRORMESSAGE
|
15
|
+
SSH returned error while connecting to #{TasteTester::Config.user}@#{@host}
|
16
|
+
The host might be broken or your SSH access is not working properly
|
17
|
+
Try doing
|
18
|
+
|
19
|
+
#{ssh_base_cmd} -v
|
20
|
+
|
21
|
+
to see if ssh connection is good.
|
22
|
+
If ssh works, add '-v' key to taste-tester to see the list of commands it's
|
23
|
+
trying to execute, and try to run them manually on destination host
|
24
|
+
ERRORMESSAGE
|
25
|
+
logger.error(error)
|
26
|
+
fail TasteTester::Exceptions::SshError
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_ssh_cmd(ssh, command_list)
|
30
|
+
if TasteTester::Config.windows_target
|
31
|
+
# Powershell has no `&&`. So originally we looked into joining the
|
32
|
+
# various commands with `; if ($LASTEXITCODE -ne 0) { exit 42 }; `
|
33
|
+
# except that it turns out lots of Powershell commands don't set
|
34
|
+
# $LASTEXITCODE and so that crashes a lot.
|
35
|
+
#
|
36
|
+
# There is an `-and`, but it only works if you group things together
|
37
|
+
# with `()`, but that loses any output.
|
38
|
+
#
|
39
|
+
# Technically in the latest preview of Powershell 7, `&&` exists, but
|
40
|
+
# we cannot rely on this.
|
41
|
+
#
|
42
|
+
# So here we are. Thanks Windows Team.
|
43
|
+
#
|
44
|
+
# Anyway, what we *really* care about is that we exit if we_testing()
|
45
|
+
# errors out, and on Windows, we can do that straight from the
|
46
|
+
# powershell we generate there (we're not forking off awk), so the
|
47
|
+
# `&&` isn't as critical. It's still a bummer that we continue on
|
48
|
+
# if one of the commands fails, but... Well, it's Windows,
|
49
|
+
# whatchyagonnado?
|
50
|
+
|
51
|
+
cmds = command_list.join(' ; ')
|
52
|
+
else
|
53
|
+
cmds = command_list.join(' && ')
|
54
|
+
end
|
55
|
+
cmd = ssh
|
56
|
+
cc = Base64.encode64(cmds).delete("\n")
|
57
|
+
if TasteTester::Config.windows_target
|
58
|
+
|
59
|
+
# This is pretty horrible, but because there's no way I can find to
|
60
|
+
# take base64 as stdin and output text, we end up having to do use
|
61
|
+
# these PS functions. But they're going to pass through *both* bash
|
62
|
+
# *and* powershell, so in order to preserve the quotes, it gets
|
63
|
+
# pretty ugly.
|
64
|
+
#
|
65
|
+
# The tldr here is that in shell you can't escape quotes you're
|
66
|
+
# using to quote something. So if you use single quotes, there's no
|
67
|
+
# way to escape a single quote inside, and same with double-quotes.
|
68
|
+
# As such we switch between quote-styles as necessary. As long as the
|
69
|
+
# strings are back-to-back, shell handles this well. To make this
|
70
|
+
# clear, imagine you want to echo this:
|
71
|
+
# '"'"
|
72
|
+
# Exactly like that. You would quote the first single quotes in double
|
73
|
+
# quotes: "'"
|
74
|
+
# Then the double quotes in single quotes: '"'
|
75
|
+
# Now repeat twice and you get: echo "'"'"'"'"'"'
|
76
|
+
# And that works reliably.
|
77
|
+
#
|
78
|
+
# We're doing the same thing here. What we want on the other side of
|
79
|
+
# the ssh is:
|
80
|
+
# [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String('...'))
|
81
|
+
#
|
82
|
+
# But for this to work right the command we pass to SSH has to be in
|
83
|
+
# single quotes too. For simplicity lets call those two functions
|
84
|
+
# above GetString() and Base64(). So we'll start with:
|
85
|
+
# ssh host 'GetString(Base64('
|
86
|
+
# We've closed that string, now we add the single quote we want there,
|
87
|
+
# as well as the stuff inside of those double quotes, so we'll add:
|
88
|
+
# '#{cc}'))
|
89
|
+
# but that must be in double quotes since we're using single quotes.
|
90
|
+
# Put that together:
|
91
|
+
# ssh host 'GetString(Base64('"'#{cc}'))"
|
92
|
+
# ^-----------------^^---------^
|
93
|
+
# string 1 string2
|
94
|
+
# No we're doing with needing single quotes inside of our string, go
|
95
|
+
# back to using single-quotes so no variables get interpolated. We now
|
96
|
+
# add: ' | powershell.exe -c -; exit $LASTEXITCODE'
|
97
|
+
# ssh host 'GetString(Base64('"'#{cc}'))"' | powershell.exe ...'
|
98
|
+
# ^-----------------^^---------^^---------------------^
|
99
|
+
#
|
100
|
+
# More than you ever wanted to know about shell. You're welcome.
|
101
|
+
#
|
102
|
+
# But now we have to put it inside of a ruby string, :)
|
103
|
+
|
104
|
+
# just for readability, put these crazy function names inside of
|
105
|
+
# variables
|
106
|
+
fun1 = '[Text.Encoding]::Utf8.GetString'
|
107
|
+
fun2 = '[Convert]::FromBase64String'
|
108
|
+
cmd += "'#{fun1}(#{fun2}('\"'#{cc}'))\"' | "
|
109
|
+
# ^----------------^ ^----------^^---
|
110
|
+
# single-q double-q single-q
|
111
|
+
# string 1 string2 string3
|
112
|
+
cmd += 'powershell.exe -c -; exit $LASTEXITCODE\''
|
113
|
+
# ----------------------------------------^
|
114
|
+
# continued string3
|
115
|
+
else
|
116
|
+
cmd += "\"echo '#{cc}' | base64 --decode"
|
117
|
+
if TasteTester::Config.user != 'root'
|
118
|
+
cmd += ' | sudo bash -x"'
|
119
|
+
else
|
120
|
+
cmd += ' | bash -x"'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
cmd
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/taste_tester/state.rb
CHANGED
@@ -91,7 +91,10 @@ module TasteTester
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def bundle
|
94
|
-
TasteTester::State.read(:bundle)
|
94
|
+
val = TasteTester::State.read(:bundle)
|
95
|
+
# promote value to symbol to match config value.
|
96
|
+
return :compatible if val == 'compatible'
|
97
|
+
val
|
95
98
|
end
|
96
99
|
|
97
100
|
def bundle=(bundle)
|
@@ -116,7 +119,7 @@ module TasteTester
|
|
116
119
|
|
117
120
|
def real_wipe
|
118
121
|
if TasteTester::Config.ref_file &&
|
119
|
-
File.
|
122
|
+
File.exist?(TasteTester::Config.ref_file)
|
120
123
|
File.delete(TasteTester::Config.ref_file)
|
121
124
|
end
|
122
125
|
end
|
data/lib/taste_tester/tunnel.rb
CHANGED
@@ -14,62 +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
30
|
def initialize(host, server)
|
26
31
|
@host = host
|
27
32
|
@server = server
|
28
|
-
if TasteTester::Config.testing_until
|
29
|
-
@delta_secs = TasteTester::Config.testing_until.strftime('%s').to_i -
|
30
|
-
Time.now.strftime('%s').to_i
|
31
|
-
else
|
32
|
-
@delta_secs = TasteTester::Config.testing_time
|
33
|
-
end
|
34
33
|
end
|
35
34
|
|
36
35
|
def run
|
37
36
|
@port = TasteTester::Config.tunnel_port
|
38
37
|
logger.info("Setting up tunnel on port #{@port}")
|
39
|
-
|
38
|
+
exec!(cmd, logger)
|
40
39
|
rescue StandardError => e
|
41
40
|
logger.error "Failed bringing up ssh tunnel: #{e}"
|
42
|
-
|
41
|
+
error!
|
43
42
|
end
|
44
43
|
|
45
44
|
def cmd
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
" sleep #{@delta_secs}"
|
45
|
+
if TasteTester::Config.windows_target
|
46
|
+
cmds = windows_tunnel_cmd
|
47
|
+
else
|
48
|
+
cmds = sane_os_tunnel_cmd
|
49
|
+
end
|
50
|
+
|
53
51
|
# As great as it would be to have ExitOnForwardFailure=yes,
|
54
52
|
# we had multiple cases of tunnels dying
|
55
53
|
# if -f and ExitOnForwardFailure are used together.
|
56
54
|
# In most cases the first request from chef was "breaking" the tunnel,
|
57
55
|
# in a way that port was still open, but subsequent requests were hanging.
|
58
56
|
# This is reproducible and should be looked into.
|
59
|
-
cmd = "#{
|
60
|
-
|
61
|
-
|
62
|
-
'-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' +
|
63
|
-
"-o ServerAliveInterval=10 -o ServerAliveCountMax=#{@max_ping} " +
|
64
|
-
"-f -R #{@port}:localhost:#{@server.port} "
|
65
|
-
if TasteTester::Config.user != 'root'
|
66
|
-
cc = Base64.encode64(cmds).delete("\n")
|
67
|
-
cmd += "#{TasteTester::Config.user}@#{@host} \"echo '#{cc}' | base64" +
|
68
|
-
' --decode | sudo bash -x"'
|
69
|
-
else
|
70
|
-
cmd += "root@#{@host} '#{cmds}'"
|
71
|
-
end
|
72
|
-
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])
|
73
60
|
end
|
74
61
|
|
75
62
|
def self.kill(name)
|
@@ -78,15 +65,165 @@ module TasteTester
|
|
78
65
|
# surround this in paryns, and make sure as a whole it evaluates
|
79
66
|
# to true so it doesn't mess up other things... even though this is
|
80
67
|
# the only thing we're currently executing in this SSH.
|
81
|
-
if TasteTester::Config.
|
82
|
-
|
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
83
|
end
|
84
|
-
cmd = "( [ -s #{TasteTester::Config.timestamp_file} ]" +
|
85
|
-
" && #{sudo}kill -9 -- " +
|
86
|
-
"-\$(cat #{TasteTester::Config.timestamp_file}) 2>/dev/null; " +
|
87
|
-
' true )'
|
88
84
|
ssh << cmd
|
89
85
|
ssh.run!
|
90
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 ($true) {
|
115
|
+
if (-Not (Test-Path $ts)) {
|
116
|
+
# if we are here, we know we've created our source
|
117
|
+
$splat = @{
|
118
|
+
LogName = "Application"
|
119
|
+
Source = "taste-tester"
|
120
|
+
EventID = 5
|
121
|
+
EntryType = "Information"
|
122
|
+
Message = "Ending tunnel: timestamp file disappeared"
|
123
|
+
}
|
124
|
+
Write-EventLog @splat
|
125
|
+
break
|
126
|
+
}
|
127
|
+
sleep 60
|
128
|
+
}
|
129
|
+
done
|
130
|
+
EOS
|
131
|
+
end
|
132
|
+
|
133
|
+
def sane_os_tunnel_cmd
|
134
|
+
@ts = TasteTester::Config.testing_end_time.strftime('%y%m%d%H%M.%S')
|
135
|
+
# Tie the life of our SSH tunnel with the life of timestamp file.
|
136
|
+
# taste-testing can be renewed, so we'll wait until:
|
137
|
+
# 1. the timestamp file is entirely gone
|
138
|
+
# 2. our parent sshd process dies
|
139
|
+
# 3. new taste-tester instance is running (file contains different PGID)
|
140
|
+
<<~EOS
|
141
|
+
log() {
|
142
|
+
[ -e /usr/bin/logger ] || return
|
143
|
+
logger -t taste-tester "$*"
|
144
|
+
}
|
145
|
+
# sets $current_pgid
|
146
|
+
# This is important, this should just be called ald let it set the
|
147
|
+
# variable. Do NOT call in a subshell like foo=$(get_current_pgid)
|
148
|
+
# as then you end up even further down the list of children
|
149
|
+
get_current_pgid() {
|
150
|
+
|
151
|
+
# if TT user is non-root, then it breaks down like this:
|
152
|
+
# we are 'bash'
|
153
|
+
# our parent is 'sudo'
|
154
|
+
# our parent's parent is 'bash "echo ..." | sudo bash -x'
|
155
|
+
# our parent's parent's parent is ssh
|
156
|
+
# - we want the progress-group ID of *that*
|
157
|
+
#
|
158
|
+
# EXCEPT... sometimes sudo forks itself one more time so it's
|
159
|
+
# we are 'bash'
|
160
|
+
# our parent is 'sudo'
|
161
|
+
# our parent's parent 'sudo'
|
162
|
+
# our parent's parent's parent is 'bash "echo ..." | sudo bash -x'
|
163
|
+
# our parent's parent's parent's parent is ssh
|
164
|
+
# - we want the progress-group ID of *that*
|
165
|
+
#
|
166
|
+
# BUT if the TT user is root, no sudo at all...
|
167
|
+
# we are 'bash'
|
168
|
+
# our parent is 'bash "echo ..." | bash -c
|
169
|
+
# our parent's parent is ssh
|
170
|
+
# - we want the progress-group ID of *that*
|
171
|
+
#
|
172
|
+
# We can make all sorts of assumptions, but the most reliable way
|
173
|
+
# to do this that's always correct is to is simply to walk parents until
|
174
|
+
# we hit something with SSH in the name. Start with PPID and go from
|
175
|
+
# there.
|
176
|
+
#
|
177
|
+
# There's a few commented out 'log's here that are too verbose
|
178
|
+
# for operation (since this function runs every minute) but are useful
|
179
|
+
# for debugging.
|
180
|
+
|
181
|
+
relevant_pid=''
|
182
|
+
current_pid=$PPID
|
183
|
+
while true; do
|
184
|
+
name=$(ps -o command= -p $current_pid)
|
185
|
+
if [[ "$name" =~ sshd ]]; then
|
186
|
+
# Uncomment the following for debugging...
|
187
|
+
#log "$current_pid is ssh, that's us!"
|
188
|
+
relevant_pid=$current_pid
|
189
|
+
break
|
190
|
+
fi
|
191
|
+
# Uncomment the following for debugging...
|
192
|
+
#log "$current_pid is $name, finding parent..."
|
193
|
+
current_pid=$(ps -o ppid= -p $current_pid)
|
194
|
+
done
|
195
|
+
if [ -z "$relevant_pid" ];then
|
196
|
+
log "Cannot determine relevant PGID"
|
197
|
+
exit 42
|
198
|
+
fi
|
199
|
+
current_pgid="$(ps -o pgid= -p $relevant_pid | sed "s| ||g")"
|
200
|
+
# Uncomment the following for debugging...
|
201
|
+
#log "PGID of ssh ($relevant_pid) is $current_pgid"
|
202
|
+
}
|
203
|
+
get_current_pgid
|
204
|
+
SSH_PGID=$current_pgid
|
205
|
+
|
206
|
+
echo $SSH_PGID > #{TasteTester::Config.timestamp_file} && \
|
207
|
+
# TODO: pull this from Host.touchcmd
|
208
|
+
touch -t #{@ts} #{TasteTester::Config.timestamp_file} && \
|
209
|
+
while true; do
|
210
|
+
if ! [ -f "#{TasteTester::Config.timestamp_file}" ]; then
|
211
|
+
log "Ending tunnel: timestamp file disappeared"
|
212
|
+
break
|
213
|
+
fi
|
214
|
+
current_pid="$(cat #{TasteTester::Config.timestamp_file})"
|
215
|
+
if ! [ "$current_pid" = "$SSH_PGID" ]; then
|
216
|
+
log "Ending tunnel: timestamp PGID changed"
|
217
|
+
break
|
218
|
+
fi
|
219
|
+
get_current_pgid
|
220
|
+
if ! [ "$current_pgid" = "$SSH_PGID" ]; then
|
221
|
+
log "Ending tunnel: timestamp PGID isn't ours"
|
222
|
+
break
|
223
|
+
fi
|
224
|
+
sleep 60
|
225
|
+
done
|
226
|
+
EOS
|
227
|
+
end
|
91
228
|
end
|
92
229
|
end
|