Publish curated Unraid-AppOps-Gateway source from ba66029223865607deddc234e283c11127324eb8

This commit is contained in:
Public Source Publisher committed 2026-10-07 01:56:22 +00:00
1 parent 820244dbb3
commit 8dd4b40252
52 files changed
+11814 -288

No files matched your search

@@ -10,16 +10,17 @@ import httpx
from ..connectors.portainer import PortainerConnector
from ..core.config import settings
from ..core.redaction import redact_message
from .controller_artifact_helper_spec import (
_IMAGE_ID,
_REVISION,
CONTROLLER_VERSION,
HELPER_LABEL,
HELPER_NAME,
SOURCE_REPOSITORY as SOURCE_REPOSITORY,
_IMAGE_ID,
_REVISION,
helper_container_spec,
)
from .controller_artifact_helper_spec import (
SOURCE_REPOSITORY as SOURCE_REPOSITORY,
)
from .deployment_broker import DeploymentBrokerClient
GITEA_OWNER = "Jens"
@@ -28,6 +29,132 @@ REQUIRED_PUSH_CONTEXT = "Managed validation / full (push)"
CONTROLLER_CONTAINER = "gitea-deploy-control"
_HELPER_TIMEOUT_SECONDS = 240
_TIMESTAMP = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$")
_MAX_HELPER_LOG_CHARS = 4000
_MAX_HELPER_LOG_LINES = 200
_MAX_HELPER_LOG_WIRE_BYTES = _MAX_HELPER_LOG_CHARS + (8 * _MAX_HELPER_LOG_LINES)
_HELPER_DIAGNOSTIC_PREFIX = "APPOPS_CONTROLLER_PROMOTION_DIAGNOSTIC"
_HELPER_DIAGNOSTIC_CODES = frozenset({"helper_failed"})
_HELPER_DIAGNOSTIC_PHASES = frozenset(
{
"shell_setup",
"tmp_root",
"workspace_create",
"workspace_validate",
"tool_git",
"tool_docker",
"tool_compose",
"tool_python",
"tool_tar",
"tool_curl",
"source_commit",
"source_tree",
"source_archive",
"archive_permissions",
"archive_digest",
"upgrade_script",
"upgrade_permissions",
"upgrade",
"cleanup",
}
)
_HELPER_DIAGNOSTIC_LINE = re.compile(
rf"^{_HELPER_DIAGNOSTIC_PREFIX} code=([a-z_]+) phase=([a-z_]+) "
r"exitCode=(0|[1-9][0-9]{0,2})$"
)
def helper_failure_diagnostics(exit_code: Any) -> dict[str, Any]:
safe_exit_code = exit_code if type(exit_code) is int and 0 <= exit_code <= 255 else None
return {"code": "helper_failed", "phase": "unknown", "exitCode": safe_exit_code}
def safe_helper_diagnostics(value: Any) -> dict[str, Any] | None:
if not isinstance(value, dict) or set(value) != {"code", "phase", "exitCode"}:
return None
code = value.get("code")
phase = value.get("phase")
exit_code = value.get("exitCode")
if not isinstance(code, str) or code not in _HELPER_DIAGNOSTIC_CODES:
return None
if not isinstance(phase, str) or (
phase != "unknown" and phase not in _HELPER_DIAGNOSTIC_PHASES
):
return None
if exit_code is not None and (
type(exit_code) is not int or not 0 <= exit_code <= 255
):
return None
return {"code": code, "phase": phase, "exitCode": exit_code}
def parse_helper_diagnostics(log_text: Any, exit_code: Any) -> dict[str, Any]:
fallback = helper_failure_diagnostics(exit_code)
if (
fallback["exitCode"] is None
or not isinstance(log_text, str)
or len(log_text) > _MAX_HELPER_LOG_CHARS
):
return fallback
candidates = [
line
for line in log_text.splitlines()
if line.startswith(_HELPER_DIAGNOSTIC_PREFIX)
]
if len(candidates) != 1:
return fallback
match = _HELPER_DIAGNOSTIC_LINE.fullmatch(candidates[0])
if match is None:
return fallback
code, phase, marker_exit_code = match.groups()
parsed_exit_code = int(marker_exit_code)
if (
code not in _HELPER_DIAGNOSTIC_CODES
or phase not in _HELPER_DIAGNOSTIC_PHASES
or parsed_exit_code != fallback["exitCode"]
):
return fallback
return {"code": code, "phase": phase, "exitCode": parsed_exit_code}
def _decode_docker_log_stream(content: Any) -> str | None:
if (
not isinstance(content, bytes)
or not content
or len(content) > _MAX_HELPER_LOG_WIRE_BYTES
):
return None
offset = 0
frame_count = 0
payload_size = 0
payloads: list[bytes] = []
while offset < len(content):
if frame_count >= _MAX_HELPER_LOG_LINES or len(content) - offset < 8:
return None
header = content[offset : offset + 8]
stream_type = header[0]
if stream_type not in {1, 2} or header[1:4] != b"\x00\x00\x00":
return None
frame_size = int.from_bytes(header[4:8], "big")
if frame_size == 0 or frame_size > _MAX_HELPER_LOG_CHARS - payload_size:
return None
payload_start = offset + 8
payload_end = payload_start + frame_size
if payload_end > len(content):
return None
payloads.append(content[payload_start:payload_end])
payload_size += frame_size
frame_count += 1
offset = payload_end
try:
return b"".join(payloads).decode("utf-8")
except UnicodeDecodeError:
return None
class ControllerArtifactPromotionError(RuntimeError):
@@ -257,10 +384,29 @@ class ControllerArtifactPromoter:
}
exit_code = state.get("ExitCode")
logs = self.portainer.get_container_logs(helper_id, tail=200)
log_lines = logs.get("logs") if isinstance(logs, dict) and isinstance(logs.get("logs"), list) else []
tail = "\n".join(str(line) for line in log_lines[-40:])[-4000:]
if exit_code != 0:
if type(exit_code) is not int or exit_code != 0:
try:
logs_response = self.portainer._request(
"GET",
f"/api/endpoints/{endpoint}/docker/containers/{quote(helper_id, safe='')}/logs",
params={
"stdout": 1,
"stderr": 1,
"tail": _MAX_HELPER_LOG_LINES,
},
)
log_text = (
_decode_docker_log_stream(logs_response.content)
if isinstance(logs_response, httpx.Response)
and logs_response.status_code == 200
else None
)
except (httpx.HTTPError, RuntimeError, ValueError):
log_text = None
diagnostics = parse_helper_diagnostics(
log_text,
exit_code,
)
try:
self._remove_helper(helper_id)
except RuntimeError:
@@ -269,15 +415,14 @@ class ControllerArtifactPromoter:
"status": "failed",
"revision": revision,
"reason": "controller_promoter_helper_failed",
"exitCode": exit_code,
"logTail": redact_message(tail),
"diagnostics": diagnostics,
}
try:
self._remove_helper(helper_id)
except RuntimeError:
pass
return {"status": "promoted", "revision": revision, "logTail": redact_message(tail)}
return {"status": "promoted", "revision": revision}
def run(self) -> dict[str, Any]:
revision = self._validated_main_revision()