Rebuild beta and campaign file activations
This commit is contained in:
+45
-12
@@ -35,7 +35,7 @@ import urllib.parse
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
VERSION = "1.9.1"
|
||||
VERSION = "1.9.2"
|
||||
LABEL = "io.electric-cherry"
|
||||
MAX_FILE = 32 * 1024 * 1024
|
||||
MANIFEST_MAX_ENTRIES = 10000
|
||||
@@ -1504,7 +1504,7 @@ class Agent:
|
||||
if line.startswith("PRETTY_NAME="):
|
||||
os_name = line.split("=", 1)[1].strip('"')
|
||||
update = self.update_inventory()
|
||||
capabilities = ["library.remove", "plutonium.cache", "instance.cancel-create", "instance.safety-override", "instance.unhealthy-recovery", "instance.autoscaling-remove", "operations.read-lane", "operations.health-lane", "rcon.status-batch", "instance.heartbeat-state", "files.manifest", "files.blobs", "logs.read-offset", "bank.settlement"] + (["host.decommission"] if self.can_uninstall() else []) + (["agent.update"] if update["enabled"] else [])
|
||||
capabilities = ["library.remove", "plutonium.cache", "instance.cancel-create", "instance.safety-override", "instance.unhealthy-recovery", "instance.rebuild-files", "instance.autoscaling-remove", "operations.read-lane", "operations.health-lane", "rcon.status-batch", "instance.heartbeat-state", "files.manifest", "files.blobs", "logs.read-offset", "bank.settlement"] + (["host.decommission"] if self.can_uninstall() else []) + (["agent.update"] if update["enabled"] else [])
|
||||
plutonium_cache = self.plutonium_cache_inventory()
|
||||
return {"hostname": socket.gethostname(), "agentVersion": VERSION, "protocolVersion": 1, "capabilities": capabilities, "agentUpdate": update, "agentLanes": self.lane_inventory(), "os": os_name, "osVersion": platform.release(), "architecture": platform.machine(), "cpu": platform.processor() or platform.machine(), "cpuCount": os.cpu_count() or 0, **resources, **({"cpuUsagePercent": cpu_usage} if cpu_usage is not None else {}), "memoryTotal": memory.get("MemTotal", 0), "memoryAvailable": memory.get("MemAvailable", 0), "storageTotal": usage.total, "storageAvailable": usage.free, "dockerVersion": docker_version, "dockerRunning": docker_running, "directoriesWritable": os.access(self.root, os.W_OK), "runtimeImages": sorted(set(images)), "preparedRuntimeImages": sorted(set(prepared_images)), **({"plutoniumCache": plutonium_cache} if plutonium_cache else {}), "libraries": libraries, "libraryCaches": caches, "currentOperation": self.current_operation, "portRange": self.config.get("portRange", {"min": 28960, "max": 29060})}
|
||||
|
||||
@@ -1889,7 +1889,7 @@ class Agent:
|
||||
managed_write(app, marker, raw, uid, gid)
|
||||
return True
|
||||
|
||||
def create(self, instance_id, generation, payload):
|
||||
def create(self, instance_id, generation, payload, *, before=None):
|
||||
self.operation_phase = "checking-instance"
|
||||
self.check_creation_cancellation()
|
||||
directory = self.instance_dir(instance_id, generation)
|
||||
@@ -1930,7 +1930,7 @@ class Agent:
|
||||
args.append(image_id)
|
||||
self.check_creation_cancellation()
|
||||
self.operation_phase = "creating-container"
|
||||
self.checkpoint({"phase": "creating", "generation": generation, "specSha256": spec_sha})
|
||||
self.checkpoint({"phase": "creating", "generation": generation, "specSha256": spec_sha, **({"before": before} if before is not None else {})})
|
||||
self.docker.call(*args, timeout=120)
|
||||
self.check_creation_cancellation()
|
||||
self.operation_phase = "starting-container"
|
||||
@@ -2126,7 +2126,11 @@ class Agent:
|
||||
self.safe_to_disrupt(instance_id, generation, allow_unhealthy=unhealthy_recovery)
|
||||
if "autoscaling" in payload:
|
||||
self.authorize_autoscaling_removal(instance_id, generation, payload)
|
||||
self.checkpoint({"phase": "disrupting", "containerId": health.get("containerId"), "startedAt": health.get("startedAt")})
|
||||
if kind == "instance.rebuild" and payload.get("stageId"):
|
||||
before = self.rebuild_before(instance_id, generation, payload["stageId"], spec)
|
||||
if not override_safety:
|
||||
self.safe_to_disrupt(instance_id, generation)
|
||||
self.checkpoint({"phase": "disrupting", "containerId": health.get("containerId"), "startedAt": health.get("startedAt"), **({"before": before} if kind == "instance.rebuild" and payload.get("stageId") else {})})
|
||||
if payload.get("stageId") and kind == "instance.restart":
|
||||
before = self.activate(instance_id, generation, payload["stageId"], override_safety=override_safety)
|
||||
if kind in ("instance.remove", "instance.rebuild"):
|
||||
@@ -2135,12 +2139,12 @@ class Agent:
|
||||
self.docker.call("container", "rm", name)
|
||||
self.settle_bank(instance_id, generation)
|
||||
if kind == "instance.rebuild":
|
||||
result = self.create(instance_id, next_generation, spec)
|
||||
result = self.create(instance_id, next_generation, spec, before=before if payload.get("stageId") else None)
|
||||
self.operation_phase = "removing-old-instance"
|
||||
old = self.instance_dir(instance_id, generation)
|
||||
if old.exists():
|
||||
shutil.rmtree(old, ignore_errors=False)
|
||||
return result
|
||||
return {**result, "before": before}
|
||||
directory = self.instance_dir(instance_id, generation)
|
||||
if directory.exists():
|
||||
shutil.rmtree(directory)
|
||||
@@ -2235,6 +2239,29 @@ class Agent:
|
||||
atomic_json(marker, manifest)
|
||||
return {"stageId": stage_id, "files": len(planned), "state": "staged"}
|
||||
|
||||
def rebuild_before(self, instance_id, generation, stage_id, spec):
|
||||
"""Validate requested replacement bytes and snapshot the old generation before teardown."""
|
||||
directory = self.instance_dir(instance_id, generation)
|
||||
marker = directory / "stages" / identifier(stage_id) / "manifest.json"
|
||||
saved = json.loads(marker.read_text())
|
||||
if saved["state"] != "staged":
|
||||
raise Rejected("Rebuild requires an unactivated file stage")
|
||||
replacement = {item["path"]: item for item in self.initial_files(spec)}
|
||||
before = []
|
||||
for item in saved["files"]:
|
||||
target = replacement.get(item["path"])
|
||||
if item["delete"]:
|
||||
if target is not None:
|
||||
raise Rejected("Rebuild includes a file requested for removal")
|
||||
elif target is None or digest(self.decode_file(target)) != item["sha256"]:
|
||||
raise Rejected("Rebuild does not match staged file content")
|
||||
content = managed_read(directory / "app", item["path"])
|
||||
actual_sha = None if content is None else digest(content)
|
||||
if item.get("checkBefore") and actual_sha != item.get("expectedBeforeSha"):
|
||||
raise Rejected("Target changed after campaign review")
|
||||
before.append({"id": item.get("id"), "path": item["path"], "contentBase64": None if content is None else base64.b64encode(content).decode()})
|
||||
return before
|
||||
|
||||
def activate(self, instance_id, generation, stage_id, *, override_safety=False):
|
||||
directory = self.instance_dir(instance_id, generation)
|
||||
stage = directory / "stages" / identifier(stage_id)
|
||||
@@ -2966,11 +2993,17 @@ class Agent:
|
||||
health = self.inspect(instance_id, observed_generation)
|
||||
before = []
|
||||
if payload.get("stageId"):
|
||||
stage = self.instance_dir(instance_id, generation) / "stages" / identifier(payload["stageId"]) / "manifest.json"
|
||||
saved = json.loads(stage.read_text())
|
||||
if saved["state"] != "activated":
|
||||
return row
|
||||
before = saved["before"]
|
||||
if kind == "instance.rebuild":
|
||||
# The creating checkpoint retains snapshots after old storage is deleted.
|
||||
if not isinstance(checkpoint.get("before"), list):
|
||||
return row
|
||||
before = checkpoint["before"]
|
||||
else:
|
||||
stage = self.instance_dir(instance_id, generation) / "stages" / identifier(payload["stageId"]) / "manifest.json"
|
||||
saved = json.loads(stage.read_text())
|
||||
if saved["state"] != "activated":
|
||||
return row
|
||||
before = saved["before"]
|
||||
proven = False
|
||||
if kind in ("instance.create", "instance.rebuild"):
|
||||
proven = health["running"] and health.get("creationOperation") == operation["id"] and health.get("specSha256") == checkpoint.get("specSha256")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.2",
|
||||
"protocolVersion": 1,
|
||||
"platform": "debian-amd64",
|
||||
"sha256": "c3c9fc251c93802e8016b67dc3fba409df00de6681751b9ba231b80d702c7333",
|
||||
"sha256": "acff8fdbf75652821638a9c90ee9cc4ef5254bc2f19fabc436b9135f93307a5a",
|
||||
"updaterProtocolVersion": 2,
|
||||
"updaterSha256": "a9d7f96b87d0ec74d8a0f476dff6e3d2a7a0bc454a93a51d7b57de00c9ce23cb",
|
||||
"installerSha256": "384d5e397a51d19cb9fc9ed8793b4314353f726e217030246050859c0bc18231"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSpocb6VBfQzvdIh9HF6fdSNniN+gUCas2WORsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwwLDMACgkQxen3UjZ4jfrMYQEA3BJSdUa3uJW7Jz8J5+Ia
|
||||
v+zC8uuzBB7nXgW4gtbHguYA/i0M3faiIr2eQ4Og+fKqh2TVyAC2BhMbwBPbPaft
|
||||
JwIA
|
||||
=x+Vg
|
||||
-----END PGP SIGNATURE-----
|
||||
Reference in New Issue
Block a user