sandbox-adapter 0.1.1 → 0.1.2
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/lib/sandbox/adapter/box.rb +36 -14
- data/lib/sandbox/adapter/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8621cc441678bf373fa97d36a3f9c34545c0482a306f6a15da3e9f873d486cec
|
|
4
|
+
data.tar.gz: d51db640d37a85125008f8ab5a71324e04fa4bdb514724792e4c9c0b1828d69a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd94042bf70fa5df59e241c48f4437136d8edd19a8946fe9710df436f344ae0847969da8fa90ca0fea7374964d19bb6ed44f95cd4d2fbafbb2517ff4bccba5ef
|
|
7
|
+
data.tar.gz: 8d53d6726db2b45498f9e3a2b1aa27516bc1443a53cae2e7af26c9f3cc040c62baae39814bcd69bdaa62e0ddb8db019836d9c5e6b7e309cf8ee034bb6b1d43d3
|
data/lib/sandbox/adapter/box.rb
CHANGED
|
@@ -30,20 +30,35 @@ class Sandbox::Adapter::Box
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Returns the exit status. env (symbol-keyed) is exported, dir is the working directory.
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
# `within` bounds a command that must not outlive a round (starting or following a detached one), on a
|
|
34
|
+
# transport that can.
|
|
35
|
+
def run(command, env: {}, dir: nil, within: nil, &)
|
|
36
|
+
line = shell(script(command, env:, dir:))
|
|
37
|
+
return @transport.exec(line, within:, &) if within && @transport.respond_to?(:bounded?)
|
|
38
|
+
@transport.exec(line, &)
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
def run!(command, **, &)
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
def run!(command, **, &block)
|
|
42
|
+
said = []
|
|
43
|
+
status = run(command, **) { |line| keep(said, line); block&.call(line) }
|
|
44
|
+
raise Failed, self.class.failure(status, command, said) unless status == 0
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# How many of the last lines a failure carries.
|
|
48
|
+
TAIL = 5
|
|
49
|
+
|
|
50
|
+
# "exit 1: what the script does", then the last lines it printed: they say what went wrong.
|
|
51
|
+
def self.failure(status, command, said)
|
|
52
|
+
[ "exit #{status}: #{headline(command)}", said.join("\n").strip.presence ].compact.join("\n")
|
|
40
53
|
end
|
|
41
54
|
|
|
42
55
|
# The first line of a script that says what it does. A shell directive is not one: every script here
|
|
43
|
-
# opens with `set -e`, and a failure named after it says nothing about what failed.
|
|
56
|
+
# opens with `set -e`, and a failure named after it says nothing about what failed. A comment first
|
|
57
|
+
# names a script whose first command would not (a function it defines).
|
|
44
58
|
DIRECTIVE = /\A(set|shopt|export|umask)\b/
|
|
45
59
|
def self.headline(script)
|
|
46
|
-
script.to_s.lines.map(&:strip).reject { _1.empty? || _1.match?(DIRECTIVE) }.first.to_s
|
|
60
|
+
line = script.to_s.lines.map(&:strip).reject { _1.empty? || _1.match?(DIRECTIVE) }.first.to_s
|
|
61
|
+
line.start_with?("#") ? line.delete_prefix("#").strip : line
|
|
47
62
|
end
|
|
48
63
|
|
|
49
64
|
# Starts `command` in its own process group under `id`, survives this connection, and streams its output from
|
|
@@ -72,9 +87,10 @@ class Sandbox::Adapter::Box
|
|
|
72
87
|
end
|
|
73
88
|
end
|
|
74
89
|
|
|
75
|
-
def exec!(id, command, **, &)
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
def exec!(id, command, **, &block)
|
|
91
|
+
said = []
|
|
92
|
+
status = exec(id, command, **) { |line| keep(said, line); block&.call(line) }
|
|
93
|
+
raise Failed, self.class.failure(status, command, said) unless status == 0
|
|
78
94
|
end
|
|
79
95
|
|
|
80
96
|
# The bytes of a file on the box, over SFTP on a fresh connection.
|
|
@@ -104,6 +120,11 @@ class Sandbox::Adapter::Box
|
|
|
104
120
|
|
|
105
121
|
private
|
|
106
122
|
|
|
123
|
+
def keep(said, line)
|
|
124
|
+
said << line
|
|
125
|
+
said.shift while said.size > TAIL
|
|
126
|
+
end
|
|
127
|
+
|
|
107
128
|
# The script runs under `as` when there is one; the login stays what it is. A login shell, for the PATH
|
|
108
129
|
# a box's profile sets, unless the transport brings its own environment (this machine).
|
|
109
130
|
def shell(script)
|
|
@@ -141,15 +162,16 @@ class Sandbox::Adapter::Box
|
|
|
141
162
|
end
|
|
142
163
|
|
|
143
164
|
# The script and its log live under scratch/id. setsid makes the wrapper the group leader (its pid is the pgid);
|
|
144
|
-
# nohup keeps it alive when this session ends; the wrapper records the exit status last
|
|
165
|
+
# nohup keeps it alive when this session ends; the wrapper records the exit status last, and a handler
|
|
166
|
+
# on TERM keeps it alive through a cancel to record the command's 143. Started already
|
|
145
167
|
# (a link dropped after the launch) is left alone.
|
|
146
168
|
def start(id, script)
|
|
147
169
|
d = "#{@dir}/#{id}"
|
|
148
|
-
run!(<<~SH, env: { NVOI_SCRIPT: script })
|
|
170
|
+
run!(<<~SH, env: { NVOI_SCRIPT: script }, within: ROUND)
|
|
149
171
|
if [ -f #{d}/pid ] && [ ! -f #{d}/exit ] && kill -0 -- -$(cat #{d}/pid) 2>/dev/null; then exit 0; fi
|
|
150
172
|
mkdir -p -m 700 #{d} && rm -f #{d}/exit
|
|
151
173
|
printf '%s\\n' "$NVOI_SCRIPT" > #{d}/script && chmod 600 #{d}/script
|
|
152
|
-
setsid nohup bash -c 'echo $$ > #{d}/pid; bash #{d}/script > #{d}/log 2>&1; echo $? > #{d}/exit' >/dev/null 2>&1 &
|
|
174
|
+
setsid nohup bash -c 'trap : TERM; echo $$ > #{d}/pid; bash #{d}/script > #{d}/log 2>&1; echo $? > #{d}/exit' >/dev/null 2>&1 &
|
|
153
175
|
for i in $(seq 1 50); do [ -f #{d}/pid ] && exit 0; sleep 0.1; done; exit 1
|
|
154
176
|
SH
|
|
155
177
|
end
|
|
@@ -173,7 +195,7 @@ class Sandbox::Adapter::Box
|
|
|
173
195
|
# A round can end while the command is mid-line - apt prints "Generating locales..." and waits - and
|
|
174
196
|
# the marker is then written onto the end of that line. What comes before it is output; the marker is
|
|
175
197
|
# still the marker. Reading it only at the start of a line loses the round's status altogether.
|
|
176
|
-
run(cmd) do |line|
|
|
198
|
+
run(cmd, within: ROUND + 30) do |line|
|
|
177
199
|
out, marker, code = line.match(MARKER)&.captures
|
|
178
200
|
yield out if out.present? && status.nil? && !more
|
|
179
201
|
if marker == "more" then more = true
|