Restrict launches to prepared runtime images

This commit is contained in:
2026-10-05 23:26:18 -04:00
parent 3952c4c938
commit f8ac2436b5
11 changed files with 161 additions and 29 deletions
+4 -2
View File
@@ -32,7 +32,7 @@ import urllib.parse
import urllib.request
import uuid
VERSION = "1.5.0"
VERSION = "1.5.1"
LABEL = "io.electric-cherry"
MAX_FILE = 32 * 1024 * 1024
MANIFEST_MAX_ENTRIES = 10000
@@ -1505,7 +1505,9 @@ class Agent:
atomic_bytes(env_path, "".join(f"{key}={value}\n" for key, value in env.items()).encode())
name = self.container_name(instance_id, generation)
spec_sha = digest(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode())
args = ["container", "create", "--name", name, "--restart", "unless-stopped", "--label", LABEL + ".managed=true", "--label", LABEL + ".instance=" + instance_id, "--label", LABEL + ".generation=" + str(generation), "--label", LABEL + ".operation=" + (self.executing_id or "direct"), "--label", LABEL + ".spec-sha256=" + spec_sha, "--mount", f"type=bind,src={app},dst=/home/plutainer/app", "--mount", f"type=bind,src={library / 'content'},dst=/home/plutainer/gamefiles,readonly", "--env-file", str(env_path)]
# Do not let Docker fetch a replacement if the prepared image disappears
# between validation and container creation.
args = ["container", "create", "--pull=never", "--name", name, "--restart", "unless-stopped", "--label", LABEL + ".managed=true", "--label", LABEL + ".instance=" + instance_id, "--label", LABEL + ".generation=" + str(generation), "--label", LABEL + ".operation=" + (self.executing_id or "direct"), "--label", LABEL + ".spec-sha256=" + spec_sha, "--mount", f"type=bind,src={app},dst=/home/plutainer/app", "--mount", f"type=bind,src={library / 'content'},dst=/home/plutainer/gamefiles,readonly", "--env-file", str(env_path)]
if self.config.get("hostId"):
args.extend(["--label", LABEL + ".host=" + identifier(self.config["hostId"])])
bind_arg = "[" + bind + "]" if ":" in bind else bind
+23
View File
@@ -727,6 +727,29 @@ class AgentTests(unittest.TestCase):
self.assertEqual(second["result"], first["result"])
self.assertEqual(sum(call[:2] == ("container", "restart") for call in self.docker.calls), 1)
def test_creation_uses_cached_runtime_without_allowing_docker_to_pull(self):
payload = self.create()
creation = next(args for args in self.docker.calls if args[:2] == ("container", "create"))
self.assertIn("--pull=never", creation)
self.assertEqual(creation[-1], payload["image"])
self.assertFalse(any(args[:2] == ("image", "pull") for args in self.docker.calls))
def test_image_disappearing_after_validation_cannot_be_pulled_or_started(self):
original = self.docker.call
def missing_image(*args, **kwargs):
if args[:2] == ("container", "create"):
self.assertIn("--pull=never", args)
raise AgentError("No such image")
return original(*args, **kwargs)
payload = {"game": "t5", "libraryId": self.library_id, "image": "prepared:runtime", "port": 28960}
with patch.object(self.docker, "call", side_effect=missing_image):
with self.assertRaisesRegex(AgentError, "No such image"):
self.agent.create(INSTANCE, 1, payload)
self.assertEqual(self.docker.started, 0)
self.assertFalse(any(args[:2] == ("image", "pull") for args in self.docker.calls))
def test_manifest_is_advertised_and_reconciles_as_a_read_operation(self):
self.create()
operation = self.operation("files.manifest", {"roots": ["configs", "scripts"], "ignoreNames": [], "paths": ["configs/server.cfg"]})