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")
|
||||
|
||||
Reference in New Issue
Block a user