refactor: split control-plane cli modules
This commit is contained in:
@@ -0,0 +1,467 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. status-script module for scripts/src/platform-infra.ts.
|
||||
|
||||
// Moved mechanically from scripts/src/platform-infra.ts:3091-3538 for #903.
|
||||
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, isAbsolute, join } from "node:path";
|
||||
import type { UniDeskConfig } from "../config";
|
||||
import { rootPath } from "../config";
|
||||
import { startJob } from "../jobs";
|
||||
import type { RenderedCliResult } from "../output";
|
||||
import { pk01CaddyMergeManagedBlocksPython, renderCaddyManagedBlock, renderSimpleReverseProxyCaddySiteBlock } from "../pk01-caddy";
|
||||
import { capture, compactCapture, parseJsonOutput, prepareFrpcSecret, shQuote } from "../platform-infra-public-service";
|
||||
import { yamlBooleanField, yamlFieldLabel, yamlIntegerField } from "../platform-infra-ops-library";
|
||||
import { fingerprintSecretValues, parseEnvFile, readEnvSourceFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
|
||||
|
||||
import type { Sub2ApiConfig, Sub2ApiTargetConfig } from "./entry";
|
||||
import { requiredSecretKeys, serviceName } from "./entry";
|
||||
import { codexPoolSentinelResourceNames, imageRef, managedResourceCleanupPlan, sub2ApiProxyEnv } from "./manifest";
|
||||
|
||||
export function statusScript(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig): string {
|
||||
const expectedImage = imageRef(sub2api, target);
|
||||
const expectedUrlAllowlist = sub2api.security.urlAllowlist;
|
||||
const externalPending = target.databaseMode === "external-pending";
|
||||
const externalActive = target.databaseMode === "external-active";
|
||||
const proxy = target.egressProxy?.enabled ? target.egressProxy : null;
|
||||
const expectedProxyEnv = sub2ApiProxyEnv(target);
|
||||
const cleanupPlan = managedResourceCleanupPlan(sub2api, target);
|
||||
const appSecretName = sub2api.runtime.database.secretName;
|
||||
const redisService = sub2api.runtime.redis.serviceName;
|
||||
return `
|
||||
set -u
|
||||
tmp="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
capture_json() {
|
||||
name="$1"
|
||||
shift
|
||||
"$@" -o json >"$tmp/$name.json" 2>"$tmp/$name.err"
|
||||
rc=$?
|
||||
printf '%s' "$rc" >"$tmp/$name.rc"
|
||||
}
|
||||
capture_json ns kubectl get namespace ${target.namespace}
|
||||
capture_json deployments kubectl -n ${target.namespace} get deployments -l app.kubernetes.io/part-of=platform-infra
|
||||
capture_json statefulsets kubectl -n ${target.namespace} get statefulsets -l app.kubernetes.io/part-of=platform-infra
|
||||
capture_json pods kubectl -n ${target.namespace} get pods -l app.kubernetes.io/part-of=platform-infra
|
||||
capture_json services kubectl -n ${target.namespace} get services -l app.kubernetes.io/part-of=platform-infra
|
||||
capture_json pvc kubectl -n ${target.namespace} get pvc -l app.kubernetes.io/part-of=platform-infra
|
||||
capture_json secrets kubectl -n ${target.namespace} get secret ${appSecretName}
|
||||
capture_json configmap kubectl -n ${target.namespace} get configmap sub2api-config
|
||||
capture_json networkpolicies kubectl -n ${target.namespace} get networkpolicy
|
||||
capture_json cronjobs kubectl -n ${target.namespace} get cronjob -l app.kubernetes.io/part-of=platform-infra
|
||||
capture_json ingresses kubectl -n ${target.namespace} get ingress
|
||||
capture_json quotas kubectl -n ${target.namespace} get resourcequota
|
||||
capture_json limitranges kubectl -n ${target.namespace} get limitrange
|
||||
pod_name="$(kubectl -n ${target.namespace} get pod -l app.kubernetes.io/name=${serviceName},app.kubernetes.io/component=app -o jsonpath='{.items[0].metadata.name}' 2>"$tmp/pod-name.err" || true)"
|
||||
printf '%s' "$pod_name" >"$tmp/pod-name.txt"
|
||||
if [ -n "$pod_name" ]; then
|
||||
kubectl -n ${target.namespace} exec "$pod_name" -- sh -c 'printf "SECURITY_URL_ALLOWLIST_ENABLED=%s\\n" "$SECURITY_URL_ALLOWLIST_ENABLED"; printf "SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=%s\\n" "$SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP"; printf "SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=%s\\n" "$SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS"; printf "SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS=%s\\n" "$SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS"; printf "HTTP_PROXY=%s\\n" "$HTTP_PROXY"; printf "HTTPS_PROXY=%s\\n" "$HTTPS_PROXY"; printf "ALL_PROXY=%s\\n" "$ALL_PROXY"; printf "NO_PROXY=%s\\n" "$NO_PROXY"' >"$tmp/pod-env.out" 2>"$tmp/pod-env.err"
|
||||
printf '%s' "$?" >"$tmp/pod-env.rc"
|
||||
else
|
||||
: >"$tmp/pod-env.out"
|
||||
printf '%s\\n' 'sub2api app pod not found' >"$tmp/pod-env.err"
|
||||
printf '%s' "1" >"$tmp/pod-env.rc"
|
||||
fi
|
||||
python3 - "$tmp" <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
tmp = sys.argv[1]
|
||||
|
||||
def rc(name):
|
||||
try:
|
||||
return int(open(os.path.join(tmp, f"{name}.rc"), encoding="utf-8").read() or "1")
|
||||
except FileNotFoundError:
|
||||
return 1
|
||||
|
||||
def load(name):
|
||||
path = os.path.join(tmp, f"{name}.json")
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
try:
|
||||
return json.load(open(path, encoding="utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
def items(name):
|
||||
data = load(name)
|
||||
if not isinstance(data, dict):
|
||||
return []
|
||||
return data.get("items") or []
|
||||
|
||||
def text(name):
|
||||
path = os.path.join(tmp, name)
|
||||
try:
|
||||
return open(path, encoding="utf-8", errors="replace").read()
|
||||
except FileNotFoundError:
|
||||
return ""
|
||||
|
||||
def deployment_summary(item):
|
||||
spec = item.get("spec") or {}
|
||||
status = item.get("status") or {}
|
||||
desired = spec.get("replicas", 1)
|
||||
available = status.get("availableReplicas", 0)
|
||||
init_containers = ((spec.get("template") or {}).get("spec") or {}).get("initContainers", [])
|
||||
containers = ((spec.get("template") or {}).get("spec") or {}).get("containers", [])
|
||||
return {
|
||||
"name": item["metadata"]["name"],
|
||||
"desired": desired,
|
||||
"readyReplicas": status.get("readyReplicas", 0),
|
||||
"availableReplicas": available,
|
||||
"updatedReplicas": status.get("updatedReplicas", 0),
|
||||
"ready": available >= desired,
|
||||
"images": [c.get("image") for c in containers],
|
||||
"initImages": [c.get("image") for c in init_containers],
|
||||
}
|
||||
|
||||
def statefulset_summary(item):
|
||||
spec = item.get("spec") or {}
|
||||
status = item.get("status") or {}
|
||||
desired = spec.get("replicas", 1)
|
||||
ready = status.get("readyReplicas", 0)
|
||||
return {
|
||||
"name": item["metadata"]["name"],
|
||||
"desired": desired,
|
||||
"readyReplicas": ready,
|
||||
"currentReplicas": status.get("currentReplicas", 0),
|
||||
"updatedReplicas": status.get("updatedReplicas", 0),
|
||||
"ready": ready >= desired,
|
||||
"images": [c.get("image") for c in ((spec.get("template") or {}).get("spec") or {}).get("containers", [])],
|
||||
}
|
||||
|
||||
def pod_summary(item):
|
||||
status = item.get("status") or {}
|
||||
container_statuses = status.get("containerStatuses") or []
|
||||
return {
|
||||
"name": item["metadata"]["name"],
|
||||
"phase": status.get("phase"),
|
||||
"ready": all((cs.get("ready") is True) for cs in container_statuses) if container_statuses else False,
|
||||
"restarts": sum(int(cs.get("restartCount") or 0) for cs in container_statuses),
|
||||
"nodeName": (item.get("spec") or {}).get("nodeName"),
|
||||
"containers": [
|
||||
{
|
||||
"name": cs.get("name"),
|
||||
"ready": cs.get("ready"),
|
||||
"restartCount": cs.get("restartCount"),
|
||||
"image": cs.get("image"),
|
||||
"state": list((cs.get("state") or {}).keys()),
|
||||
"stateDetail": cs.get("state") or {},
|
||||
}
|
||||
for cs in container_statuses
|
||||
],
|
||||
"initContainers": [
|
||||
{
|
||||
"name": cs.get("name"),
|
||||
"ready": cs.get("ready"),
|
||||
"restartCount": cs.get("restartCount"),
|
||||
"image": cs.get("image"),
|
||||
"state": list((cs.get("state") or {}).keys()),
|
||||
"stateDetail": cs.get("state") or {},
|
||||
}
|
||||
for cs in (status.get("initContainerStatuses") or [])
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"type": condition.get("type"),
|
||||
"status": condition.get("status"),
|
||||
"reason": condition.get("reason"),
|
||||
"message": condition.get("message"),
|
||||
}
|
||||
for condition in status.get("conditions", [])
|
||||
],
|
||||
}
|
||||
|
||||
def service_summary(item):
|
||||
spec = item.get("spec") or {}
|
||||
return {
|
||||
"name": item["metadata"]["name"],
|
||||
"type": spec.get("type", "ClusterIP"),
|
||||
"clusterIP": spec.get("clusterIP"),
|
||||
"ports": [
|
||||
{
|
||||
"name": p.get("name"),
|
||||
"port": p.get("port"),
|
||||
"targetPort": p.get("targetPort"),
|
||||
"nodePort": p.get("nodePort"),
|
||||
}
|
||||
for p in spec.get("ports", [])
|
||||
],
|
||||
}
|
||||
|
||||
def pvc_summary(item):
|
||||
spec = item.get("spec") or {}
|
||||
status = item.get("status") or {}
|
||||
req = (spec.get("resources") or {}).get("requests") or {}
|
||||
return {
|
||||
"name": item["metadata"]["name"],
|
||||
"phase": status.get("phase"),
|
||||
"storageClassName": spec.get("storageClassName"),
|
||||
"requestedStorage": req.get("storage"),
|
||||
}
|
||||
|
||||
def network_policy_summary(item):
|
||||
spec = item.get("spec") or {}
|
||||
return {
|
||||
"name": item["metadata"]["name"],
|
||||
"podSelector": spec.get("podSelector"),
|
||||
"policyTypes": spec.get("policyTypes") or [],
|
||||
"ingress": spec.get("ingress") or [],
|
||||
"egress": spec.get("egress") or [],
|
||||
}
|
||||
|
||||
def is_allow_all_network_policy(item):
|
||||
spec = item.get("spec") or {}
|
||||
return (
|
||||
item.get("metadata", {}).get("name") == "allow-all"
|
||||
and spec.get("podSelector") == {}
|
||||
and set(spec.get("policyTypes") or []) == {"Ingress", "Egress"}
|
||||
and spec.get("ingress") == [{}]
|
||||
and spec.get("egress") == [{}]
|
||||
)
|
||||
|
||||
def resource_findings(kind, collection):
|
||||
findings = []
|
||||
for item in collection:
|
||||
spec = item.get("spec") or {}
|
||||
template_spec = ((spec.get("template") or {}).get("spec") or {})
|
||||
if template_spec.get("hostNetwork") is True:
|
||||
findings.append({"kind": kind, "name": item["metadata"]["name"], "field": "hostNetwork"})
|
||||
all_containers = [(container, "containers") for container in template_spec.get("containers", [])] + [(container, "initContainers") for container in template_spec.get("initContainers", [])]
|
||||
for container, container_group in all_containers:
|
||||
resources = container.get("resources") or {}
|
||||
if resources.get("requests"):
|
||||
findings.append({"kind": kind, "name": item["metadata"]["name"], "container": container.get("name"), "containerGroup": container_group, "field": "resources.requests"})
|
||||
if resources.get("limits"):
|
||||
findings.append({"kind": kind, "name": item["metadata"]["name"], "container": container.get("name"), "containerGroup": container_group, "field": "resources.limits"})
|
||||
for port in container.get("ports", []):
|
||||
if "hostPort" in port:
|
||||
findings.append({"kind": kind, "name": item["metadata"]["name"], "container": container.get("name"), "containerGroup": container_group, "field": "hostPort", "value": port.get("hostPort")})
|
||||
return findings
|
||||
|
||||
deployments = items("deployments")
|
||||
statefulsets = items("statefulsets")
|
||||
services = items("services")
|
||||
pods = items("pods")
|
||||
pvcs = items("pvc")
|
||||
networkpolicies = items("networkpolicies")
|
||||
cronjobs = items("cronjobs")
|
||||
secret = load("secrets")
|
||||
configmap = load("configmap")
|
||||
configmap_data = (configmap or {}).get("data") or {}
|
||||
secret_keys = sorted(((secret or {}).get("data") or {}).keys())
|
||||
missing_secret_keys = [key for key in ${JSON.stringify(requiredSecretKeys)} if key not in secret_keys]
|
||||
external_pending = ${externalPending ? "True" : "False"}
|
||||
external_active = ${externalActive ? "True" : "False"}
|
||||
expected_app_replicas = ${target.appReplicas}
|
||||
expected_redis_replicas = ${target.redisReplicas}
|
||||
cleanup_plan = json.loads(${JSON.stringify(JSON.stringify(cleanupPlan))})
|
||||
external_db_state = cleanup_plan["externalDbState"]
|
||||
redis_persistent_state = cleanup_plan["redisPersistentState"]
|
||||
public_exposure_state = cleanup_plan["publicExposure"]
|
||||
egress_proxy_state = cleanup_plan["egressProxy"]
|
||||
service_violations = []
|
||||
for svc in services:
|
||||
spec = svc.get("spec") or {}
|
||||
if spec.get("type", "ClusterIP") != "ClusterIP":
|
||||
service_violations.append({"name": svc["metadata"]["name"], "type": spec.get("type")})
|
||||
for port in spec.get("ports", []):
|
||||
if "nodePort" in port:
|
||||
service_violations.append({"name": svc["metadata"]["name"], "nodePort": port.get("nodePort")})
|
||||
resource_violations = resource_findings("Deployment", deployments) + resource_findings("StatefulSet", statefulsets)
|
||||
expected_image = "${expectedImage}"
|
||||
expected_url_allowlist = json.loads(${JSON.stringify(JSON.stringify(expectedUrlAllowlist))})
|
||||
sub2api_deployment = next((deployment_summary(item) for item in deployments if item["metadata"]["name"] == "${serviceName}"), None)
|
||||
redis_deployment = next((deployment_summary(item) for item in deployments if item["metadata"]["name"] == "${redisService}"), None)
|
||||
sub2api_desired_aligned = sub2api_deployment is not None and sub2api_deployment.get("desired") == expected_app_replicas
|
||||
redis_desired_aligned = redis_deployment is not None and redis_deployment.get("desired") == expected_redis_replicas
|
||||
image_aligned = sub2api_deployment is not None and expected_image in sub2api_deployment.get("images", [])
|
||||
url_allowlist_runtime = {
|
||||
"enabled": configmap_data.get("SECURITY_URL_ALLOWLIST_ENABLED"),
|
||||
"allowInsecureHttp": configmap_data.get("SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP"),
|
||||
"allowPrivateHosts": configmap_data.get("SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS"),
|
||||
"upstreamHosts": configmap_data.get("SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS"),
|
||||
}
|
||||
pod_env = {}
|
||||
for line in text("pod-env.out").splitlines():
|
||||
if "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
pod_env[key] = value
|
||||
url_allowlist_pod_env = {
|
||||
"enabled": pod_env.get("SECURITY_URL_ALLOWLIST_ENABLED"),
|
||||
"allowInsecureHttp": pod_env.get("SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP"),
|
||||
"allowPrivateHosts": pod_env.get("SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS"),
|
||||
"upstreamHosts": pod_env.get("SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS"),
|
||||
}
|
||||
expected_url_allowlist_strings = {
|
||||
"enabled": str(expected_url_allowlist.get("enabled")).lower(),
|
||||
"allowInsecureHttp": str(expected_url_allowlist.get("allowInsecureHttp")).lower(),
|
||||
"allowPrivateHosts": str(expected_url_allowlist.get("allowPrivateHosts")).lower(),
|
||||
"upstreamHosts": ",".join(expected_url_allowlist.get("upstreamHosts") or []),
|
||||
}
|
||||
expected_proxy_env = json.loads(${JSON.stringify(JSON.stringify({
|
||||
enabled: proxy !== null && proxy.applyToSub2Api,
|
||||
httpProxy: expectedProxyEnv.httpProxy,
|
||||
noProxy: expectedProxyEnv.noProxy,
|
||||
deploymentName: proxy?.deploymentName ?? null,
|
||||
serviceName: proxy?.serviceName ?? null,
|
||||
}))})
|
||||
url_allowlist_configmap_aligned = url_allowlist_runtime == expected_url_allowlist_strings
|
||||
url_allowlist_pod_env_aligned = rc("pod-env") == 0 and url_allowlist_pod_env == expected_url_allowlist_strings
|
||||
url_allowlist_aligned = url_allowlist_configmap_aligned and (url_allowlist_pod_env_aligned or (external_pending and expected_app_replicas == 0))
|
||||
proxy_env_runtime = {
|
||||
"HTTP_PROXY": pod_env.get("HTTP_PROXY"),
|
||||
"HTTPS_PROXY": pod_env.get("HTTPS_PROXY"),
|
||||
"ALL_PROXY": pod_env.get("ALL_PROXY"),
|
||||
"NO_PROXY": pod_env.get("NO_PROXY"),
|
||||
}
|
||||
if expected_proxy_env.get("enabled"):
|
||||
proxy_env_aligned = (
|
||||
rc("pod-env") == 0
|
||||
and proxy_env_runtime.get("HTTP_PROXY") == expected_proxy_env.get("httpProxy")
|
||||
and proxy_env_runtime.get("HTTPS_PROXY") == expected_proxy_env.get("httpProxy")
|
||||
and proxy_env_runtime.get("ALL_PROXY") == expected_proxy_env.get("httpProxy")
|
||||
and proxy_env_runtime.get("NO_PROXY") == expected_proxy_env.get("noProxy")
|
||||
)
|
||||
else:
|
||||
proxy_env_aligned = True
|
||||
allow_all_network_policy = next((item for item in networkpolicies if item.get("metadata", {}).get("name") == "allow-all"), None)
|
||||
network_policy = {
|
||||
"requiredName": "allow-all",
|
||||
"exists": allow_all_network_policy is not None,
|
||||
"ok": allow_all_network_policy is not None and is_allow_all_network_policy(allow_all_network_policy),
|
||||
"policies": [network_policy_summary(item) for item in networkpolicies],
|
||||
}
|
||||
boundary = {
|
||||
"internalOnly": len(service_violations) == 0 and len(items("ingresses")) == 0,
|
||||
"serviceViolations": service_violations,
|
||||
"ingressCount": len(items("ingresses")),
|
||||
"resourceQuotaCount": len(items("quotas")),
|
||||
"limitRangeCount": len(items("limitranges")),
|
||||
"resourceViolations": resource_violations,
|
||||
}
|
||||
deployment_summaries = [deployment_summary(item) for item in deployments]
|
||||
statefulset_summaries = [statefulset_summary(item) for item in statefulsets]
|
||||
workload_ready = all(d["ready"] for d in deployment_summaries) and all(s["ready"] for s in statefulset_summaries)
|
||||
local_postgres_present = (
|
||||
any(item.get("metadata", {}).get("name") == external_db_state["postgresStatefulSetName"] for item in statefulsets)
|
||||
or any(item.get("metadata", {}).get("name") == external_db_state["postgresServiceName"] for item in services)
|
||||
or any(item.get("metadata", {}).get("name") in [external_db_state["postgresPvcName"], external_db_state["appDataPvcName"]] for item in pvcs)
|
||||
)
|
||||
redis_pvc_present = any(item.get("metadata", {}).get("name") == redis_persistent_state["pvcName"] for item in pvcs)
|
||||
public_exposure_deployment_present = any(item.get("metadata", {}).get("name") == public_exposure_state["deploymentName"] for item in deployments)
|
||||
egress_proxy_deployment_present = any(item.get("metadata", {}).get("name") == egress_proxy_state["deploymentName"] for item in deployments)
|
||||
sentinel_cronjob_present = any(item.get("metadata", {}).get("name") == "${codexPoolSentinelResourceNames().cronJobName}" for item in cronjobs)
|
||||
standby_disabled_resources_ok = not external_pending or (
|
||||
not public_exposure_deployment_present
|
||||
and not egress_proxy_deployment_present
|
||||
and not sentinel_cronjob_present
|
||||
)
|
||||
secret_ready = len(missing_secret_keys) == 0
|
||||
secret_ok = secret_ready or external_pending
|
||||
if external_pending:
|
||||
state_model_ok = (
|
||||
not local_postgres_present
|
||||
and not redis_pvc_present
|
||||
and sub2api_desired_aligned
|
||||
and redis_desired_aligned
|
||||
and expected_app_replicas == 0
|
||||
and expected_redis_replicas == 0
|
||||
and standby_disabled_resources_ok
|
||||
)
|
||||
elif external_active:
|
||||
state_model_ok = (
|
||||
not local_postgres_present
|
||||
and not redis_pvc_present
|
||||
and sub2api_desired_aligned
|
||||
and redis_desired_aligned
|
||||
and expected_app_replicas == 1
|
||||
and expected_redis_replicas == 1
|
||||
)
|
||||
else:
|
||||
state_model_ok = local_postgres_present and sub2api_desired_aligned and redis_desired_aligned
|
||||
status_label = "pending-external-db" if external_pending else "external-db-active" if external_active else "active"
|
||||
payload = {
|
||||
"ok": rc("ns") == 0 and workload_ready and image_aligned and url_allowlist_aligned and proxy_env_aligned and network_policy["ok"] and boundary["internalOnly"] and len(resource_violations) == 0 and boundary["resourceQuotaCount"] == 0 and boundary["limitRangeCount"] == 0 and secret_ok and state_model_ok,
|
||||
"target": "${target.id}",
|
||||
"route": "${target.route}",
|
||||
"namespace": "${target.namespace}",
|
||||
"status": status_label,
|
||||
"databaseMode": "${target.databaseMode}",
|
||||
"redisMode": "${target.redisMode}",
|
||||
"expectedAppReplicas": expected_app_replicas,
|
||||
"expectedRedisReplicas": expected_redis_replicas,
|
||||
"namespaceExists": rc("ns") == 0,
|
||||
"deployments": deployment_summaries,
|
||||
"statefulsets": statefulset_summaries,
|
||||
"pods": [pod_summary(item) for item in pods],
|
||||
"services": [service_summary(item) for item in services],
|
||||
"pvcs": [pvc_summary(item) for item in pvcs],
|
||||
"networkPolicy": network_policy,
|
||||
"secret": {
|
||||
"name": "${appSecretName}",
|
||||
"exists": rc("secrets") == 0,
|
||||
"requiredKeys": ${JSON.stringify(requiredSecretKeys)},
|
||||
"missingKeys": missing_secret_keys,
|
||||
"ready": secret_ready,
|
||||
"requiredForPredeployOk": not external_pending,
|
||||
"valuesPrinted": False,
|
||||
},
|
||||
"stateModel": {
|
||||
"localPostgresPresent": local_postgres_present,
|
||||
"redisPvcPresent": redis_pvc_present,
|
||||
"sub2apiDesiredReplicasAligned": sub2api_desired_aligned,
|
||||
"redisDesiredReplicasAligned": redis_desired_aligned,
|
||||
"standbyDisabledResourcesOk": standby_disabled_resources_ok,
|
||||
"publicExposureDeploymentPresent": public_exposure_deployment_present,
|
||||
"egressProxyDeploymentPresent": egress_proxy_deployment_present,
|
||||
"sentinelCronJobPresent": sentinel_cronjob_present,
|
||||
"ok": state_model_ok,
|
||||
},
|
||||
"imageControl": {
|
||||
"desiredImage": expected_image,
|
||||
"configPath": "config/platform-infra/sub2api.yaml",
|
||||
"aligned": image_aligned,
|
||||
"runningImages": sub2api_deployment.get("images", []) if sub2api_deployment else [],
|
||||
},
|
||||
"security": {
|
||||
"urlAllowlist": {
|
||||
"configPath": "config/platform-infra/sub2api.yaml",
|
||||
"expected": expected_url_allowlist,
|
||||
"runtime": url_allowlist_runtime,
|
||||
"podEnv": url_allowlist_pod_env,
|
||||
"aligned": url_allowlist_aligned,
|
||||
"configMapAligned": url_allowlist_configmap_aligned,
|
||||
"podEnvAligned": url_allowlist_pod_env_aligned,
|
||||
"podEnvRequired": not (external_pending and expected_app_replicas == 0),
|
||||
"configMapExists": rc("configmap") == 0,
|
||||
"podEnvProbe": {
|
||||
"podName": text("pod-name.txt"),
|
||||
"exitCode": rc("pod-env"),
|
||||
"stderr": text("pod-env.err")[-1000:],
|
||||
},
|
||||
}
|
||||
},
|
||||
"egressProxy": {
|
||||
"enabled": expected_proxy_env.get("enabled"),
|
||||
"deploymentName": expected_proxy_env.get("deploymentName"),
|
||||
"serviceName": expected_proxy_env.get("serviceName"),
|
||||
"expectedHttpProxy": expected_proxy_env.get("httpProxy"),
|
||||
"expectedNoProxy": expected_proxy_env.get("noProxy"),
|
||||
"podEnv": proxy_env_runtime,
|
||||
"aligned": proxy_env_aligned,
|
||||
"valuesPrinted": False,
|
||||
},
|
||||
"boundary": boundary,
|
||||
"serviceDns": "${serviceName}.${target.namespace}.svc.cluster.local:8080",
|
||||
"next": {
|
||||
"apply": "bun scripts/cli.ts platform-infra sub2api apply --target ${target.id} --confirm",
|
||||
"validate": "bun scripts/cli.ts platform-infra sub2api validate --target ${target.id}",
|
||||
},
|
||||
}
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
sys.exit(0 if payload["ok"] else 1)
|
||||
PY
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user