hotcell-server 0.4.0 → 0.4.1
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/exe/hotcell +5 -1
- data/lib/hot_cell/server/version.rb +1 -1
- data/lib/hot_cell/supervisor.rb +54 -4
- data/lib/hot_cell/test_cell.rb +10 -5
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 390fc0f10953763de1f745cc3002fa0e548b362cbb21c82faba7dfa807b49dd5
|
|
4
|
+
data.tar.gz: fd6932e090acccea7964e38d76e65ea6163b1ebf6b5f401f6854fe8fa1e38034
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a34f827e6dbb2616e4e20b122ff6a8fdd78e40c19bfd8297c767c450a06ea4efaebc245d3ca3fb117caf13b70d922236e3c51f6a0a93207b5fc0a815e60e2583
|
|
7
|
+
data.tar.gz: '0769c12d7f26f35380dd5532edd8073c160f6a823128dcc4c88fddc697fd896be8cabec1eaa21dedf3c505a26ccda5a90cc38716a23461fa70410eb92d2ccc1e'
|
data/exe/hotcell
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
require "hot_cell/server"
|
|
5
5
|
|
|
6
|
+
development = ARGV.delete("--development") ? true : false
|
|
7
|
+
abort "usage: hotcell [--development]" unless ARGV.empty?
|
|
8
|
+
|
|
6
9
|
HotCell.load!
|
|
7
10
|
|
|
8
11
|
HotCell::Supervisor
|
|
9
|
-
.new(directory: ENV.fetch("HOTCELL_DIR", "/run/hotcell/cell"), workspace: ENV["HOTCELL_WORKSPACE"]
|
|
12
|
+
.new(directory: ENV.fetch("HOTCELL_DIR", "/run/hotcell/cell"), workspace: ENV["HOTCELL_WORKSPACE"],
|
|
13
|
+
development: development)
|
|
10
14
|
.boot
|
|
11
15
|
.run
|
data/lib/hot_cell/supervisor.rb
CHANGED
|
@@ -138,9 +138,10 @@ module HotCell
|
|
|
138
138
|
|
|
139
139
|
attr_reader :configuration, :counters, :log, :directory, :workspace
|
|
140
140
|
|
|
141
|
-
def initialize(directory:, workspace: nil,
|
|
142
|
-
ptrace_scope_path: PTRACE_SCOPE)
|
|
141
|
+
def initialize(directory:, workspace: nil, development: false, configuration: HotCell.configuration,
|
|
142
|
+
log: Log.new, ptrace_scope_path: PTRACE_SCOPE)
|
|
143
143
|
@directory = directory
|
|
144
|
+
@development_tmpdir = own_tmpdir if development
|
|
144
145
|
@workspace = workspace || File.join(tmpdir, "hotcell-workspace")
|
|
145
146
|
@configuration = configuration
|
|
146
147
|
@log = log
|
|
@@ -156,6 +157,7 @@ module HotCell
|
|
|
156
157
|
verify_socket_paths!
|
|
157
158
|
verify_limits!
|
|
158
159
|
verify_ptrace_scope!
|
|
160
|
+
claim_tmpdir if @development_tmpdir
|
|
159
161
|
verify_scratches!
|
|
160
162
|
prepare_directories
|
|
161
163
|
preload
|
|
@@ -164,7 +166,7 @@ module HotCell
|
|
|
164
166
|
@control_handler = Control.new(configuration: configuration, counters: counters)
|
|
165
167
|
trap_signals
|
|
166
168
|
|
|
167
|
-
log.write "cell.boot", pid: Process.pid, directory: directory, operations: Registry.names,
|
|
169
|
+
log.write "cell.boot", pid: Process.pid, directory: directory, tmpdir: tmpdir, operations: Registry.names,
|
|
168
170
|
configuration: configuration.to_h
|
|
169
171
|
self
|
|
170
172
|
end
|
|
@@ -1032,8 +1034,56 @@ module HotCell
|
|
|
1032
1034
|
# `Dir.tmpdir` with its fallbacks removed: it answers `.` for a `/tmp` its owner cannot write, and a
|
|
1033
1035
|
# worker can leave `/tmp` in that state. The sweep is what puts the mode back, so it has to see the
|
|
1034
1036
|
# directory `Dir.tmpdir` will answer once it has.
|
|
1037
|
+
#
|
|
1038
|
+
# A cell told no `TMPDIR` sweeps the system's: right on the accessory, whose `/tmp` is its own mount, and
|
|
1039
|
+
# wrong on a developer's machine, where `/tmp` and macOS's per-user `TMPDIR` are shared with everything
|
|
1040
|
+
# else the developer runs. So `--development` never sweeps the directory it is given, unless an explicit
|
|
1041
|
+
# workspace makes it the workspace's parent. Its scratch is a directory of its own beneath it, named for
|
|
1042
|
+
# the socket directory so two cells one uid runs do not sweep each other's slots, and chosen once here so
|
|
1043
|
+
# the default workspace and the boot agree.
|
|
1035
1044
|
def tmpdir
|
|
1036
|
-
|
|
1045
|
+
@development_tmpdir || configured_tmpdir || Etc.systmpdir
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
def configured_tmpdir
|
|
1049
|
+
ENV.values_at("TMPDIR", "TMP", "TEMP").compact.reject(&:empty?).first
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
def own_tmpdir
|
|
1053
|
+
parent = configured_tmpdir || Etc.systmpdir
|
|
1054
|
+
File.expand_path File.join(parent, "hotcell-#{directory.delete_prefix("/").tr("/", "-")}")
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
# The parent is world-writable, so the name is claimed with `mkdir` and, when it exists, read back with
|
|
1058
|
+
# `lstat`: it has to be a plain directory this uid owns. Its mode goes back to `0700` because the sticky
|
|
1059
|
+
# parent stops another uid from replacing the directory, not from writing inside one a worker opened up.
|
|
1060
|
+
# The ancestors are checked before `mkdir` and `chmod` could act through an owned symlink;
|
|
1061
|
+
# `verify_scratches!` checks them again. `ENV["TMPDIR"]` is left alone: the supervisor writes no temporary
|
|
1062
|
+
# files, a worker sets its own per request, and a second supervisor in this process would nest its
|
|
1063
|
+
# scratch inside this one.
|
|
1064
|
+
#
|
|
1065
|
+
# Accepted: an entry another uid plants inside, once a worker has opened the directory up, survives the
|
|
1066
|
+
# sweep, which removes only what this uid owns, and a slot follows it. Opening it up needs code already
|
|
1067
|
+
# running as this uid, which can reach the same files directly.
|
|
1068
|
+
def claim_tmpdir
|
|
1069
|
+
claimed = tmpdir
|
|
1070
|
+
if (link = owned_symlink_on(File.dirname(claimed)))
|
|
1071
|
+
raise ConfigurationError, "#{link} is a symlink the cell's uid owns, on the path to the scratch " \
|
|
1072
|
+
"#{claimed}, and a boot sweeps the scratch"
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
begin
|
|
1076
|
+
Dir.mkdir claimed, 0o700
|
|
1077
|
+
rescue Errno::EEXIST
|
|
1078
|
+
stat = File.lstat(claimed)
|
|
1079
|
+
unless stat.directory? && stat.uid == Process.uid
|
|
1080
|
+
raise ConfigurationError, "#{claimed} is not a directory this uid owns, and a boot sweeps it. Set " \
|
|
1081
|
+
"TMPDIR to a directory of the cell's own."
|
|
1082
|
+
end
|
|
1083
|
+
File.chmod 0o700, claimed
|
|
1084
|
+
end
|
|
1085
|
+
rescue SystemCallError => error
|
|
1086
|
+
raise ConfigurationError, "the scratch #{claimed} could not be claimed: #{error.message}"
|
|
1037
1087
|
end
|
|
1038
1088
|
|
|
1039
1089
|
def scratches
|
data/lib/hot_cell/test_cell.rb
CHANGED
|
@@ -45,7 +45,8 @@ module HotCell
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# Anything in `supervisor:` goes to Supervisor.new; everything else is the cell's own limits.
|
|
48
|
-
|
|
48
|
+
# `own_tmpdir: false` boots the cell with no `TMPDIR` at all, the way a Procfile that sets none does.
|
|
49
|
+
def initialize(name: "test", supervisor: {}, operations: nil, own_tmpdir: true, **options)
|
|
49
50
|
@name = name
|
|
50
51
|
@supervisor_options = supervisor
|
|
51
52
|
@operations = operations
|
|
@@ -54,8 +55,8 @@ module HotCell
|
|
|
54
55
|
@directory = File.join(@root, name)
|
|
55
56
|
# Boot empties `Dir.tmpdir` and the workspace's parent of what this uid owns. Inside the cell both are
|
|
56
57
|
# this directory, so the sweep reaches neither the developer's `/tmp` nor the log and sockets beside it.
|
|
57
|
-
@tmpdir = File.join(@root, "tmp")
|
|
58
|
-
@workspace = File.join(@tmpdir, "workspace")
|
|
58
|
+
@tmpdir = File.join(@root, "tmp") if own_tmpdir
|
|
59
|
+
@workspace = File.join(@tmpdir, "workspace") if own_tmpdir
|
|
59
60
|
@log_path = File.join(@root, "cell.log")
|
|
60
61
|
end
|
|
61
62
|
|
|
@@ -78,8 +79,12 @@ module HotCell
|
|
|
78
79
|
|
|
79
80
|
HotCell.limits(**@options) unless @options.empty?
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
if @tmpdir
|
|
83
|
+
FileUtils.mkdir_p @tmpdir
|
|
84
|
+
ENV["TMPDIR"] = @tmpdir
|
|
85
|
+
else
|
|
86
|
+
%w[ TMPDIR TMP TEMP ].each { |key| ENV.delete key }
|
|
87
|
+
end
|
|
83
88
|
|
|
84
89
|
supervisor = Supervisor.new(directory: directory, workspace: workspace,
|
|
85
90
|
log: Log.new(File.open(log_path, "w")), **@supervisor_options)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hotcell-server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Dalessio
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.4.
|
|
18
|
+
version: 0.4.1
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.4.
|
|
25
|
+
version: 0.4.1
|
|
26
26
|
description: |
|
|
27
27
|
Runs a HotCell container. A supervisor listens on two Unix sockets, forks a worker for each request,
|
|
28
28
|
and enforces a wall clock deadline and resource limits on it. Write the work as a subclass of
|
|
@@ -62,8 +62,8 @@ licenses:
|
|
|
62
62
|
- MIT
|
|
63
63
|
metadata:
|
|
64
64
|
homepage_uri: https://github.com/basecamp/hotcell
|
|
65
|
-
source_code_uri: https://github.com/basecamp/hotcell/tree/v0.4.
|
|
66
|
-
changelog_uri: https://github.com/basecamp/hotcell/blob/v0.4.
|
|
65
|
+
source_code_uri: https://github.com/basecamp/hotcell/tree/v0.4.1/hotcell-server
|
|
66
|
+
changelog_uri: https://github.com/basecamp/hotcell/blob/v0.4.1/CHANGELOG.md
|
|
67
67
|
bug_tracker_uri: https://github.com/basecamp/hotcell/issues
|
|
68
68
|
rubygems_mfa_required: 'true'
|
|
69
69
|
rdoc_options: []
|