a92a418d7f
Read-only export of DEV vd03 and QA vq03 (2026-11-12): the only
Children["DelmiaRecv"] user is CvdReactor.ProcessRecipeDownload (script 4,
identical on both envs; DEV 3 / QA 9 instances), and the only ScriptAlarm
.Severity reader is DEV TestMesMachine.AlarmStatus (script 42, SBTEST01/02).
QA has no #131 work.
Adds the original (rollback) and fixed bodies, a guarded check/apply/rollback
script that re-sends every script field, and the apply plan: #52 any time,
#131 with or after the dd4f1c2a binary. The old #131 body still compiles on
main but returns banded values; the fixed body is what fails to compile on the
deployed binaries. Rig evidence for both gates is in the README.
Refs #52 #131
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
101 lines
5.8 KiB
Bash
Executable File
101 lines
5.8 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Apply / roll back the Gitea #52 and #131 site-script fixes on one ScadaBridge central.
|
|
# Prepared 2026-11-12; see README.md in this folder for the order relative to the binary deploy.
|
|
#
|
|
# SCADABRIDGE_URL=http://wonder-app-vd03.zmr.zimmer.com:8085 \
|
|
# SCADABRIDGE_USERNAME=... SCADABRIDGE_PASSWORD=... \
|
|
# bash apply.sh <check|apply|rollback> <52|131> [--no-deploy]
|
|
#
|
|
# check READ-ONLY. Prints, per target script, whether the live body is the ORIGINAL (deployed
|
|
# 2026-11-12), the FIXED body, or something else, and whether each instance is stale.
|
|
# apply Guarded update: refuses unless the live body is byte-identical to original/<script>.cs
|
|
# (or already fixed -> no-op). Re-sends every other field from original/<script>.meta.json
|
|
# (`template script update` REPLACES parameters/return-def/trigger-config — omitting them
|
|
# would wipe them). Then deploys the affected instances unless --no-deploy.
|
|
# rollback The reverse: requires the live body to be fixed/<script>.cs, writes original/<script>.cs.
|
|
#
|
|
# Credentials come from the environment only (SCADABRIDGE_USERNAME / SCADABRIDGE_PASSWORD); nothing
|
|
# is passed on argv. DEV vd03 runs DisableLogin (any value works); QA needs a real login.
|
|
#
|
|
# #52 -> template CvdReactor, script ProcessRecipeDownload; deploys every CvdReactor instance.
|
|
# #131 -> template TestMesMachine, script AlarmStatus; deploys every TestMesMachine instance
|
|
# (SBTEST01/SBTEST02, DEV only — QA has no TestMesMachine and no script reading .Severity).
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO="$(cd "$HERE/../../../.." && pwd)"
|
|
export SB_CLI="${SB_CLI:-$REPO/src/ZB.MOM.WW.ScadaBridge.CLI/bin/Debug/net10.0/scadabridge}"
|
|
export SB_URL="${SCADABRIDGE_URL:?set SCADABRIDGE_URL}" SB_HERE="$HERE"
|
|
export SB_MODE="${1:?check|apply|rollback}" SB_GATE="${2:?52|131}" SB_NODEPLOY="${3:-}"
|
|
: "${SCADABRIDGE_USERNAME:?set SCADABRIDGE_USERNAME}" "${SCADABRIDGE_PASSWORD:?set SCADABRIDGE_PASSWORD}"
|
|
|
|
python3 - <<'PY'
|
|
import json, os, subprocess, sys
|
|
|
|
CLI, URL, HERE = os.environ["SB_CLI"], os.environ["SB_URL"], os.environ["SB_HERE"]
|
|
MODE, GATE, NODEPLOY = os.environ["SB_MODE"], os.environ["SB_GATE"], os.environ["SB_NODEPLOY"] == "--no-deploy"
|
|
TARGET = {"52": ("CvdReactor", "ProcessRecipeDownload"), "131": ("TestMesMachine", "AlarmStatus")}[GATE]
|
|
KEY = f"{TARGET[0]}.{TARGET[1]}"
|
|
|
|
def cli(*args, timeout=300):
|
|
r = subprocess.run([CLI, *args, "--url", URL, "--format", "json"], capture_output=True, text=True, timeout=timeout)
|
|
if r.returncode != 0:
|
|
raise SystemExit(f"FAILED {' '.join(args[:3])}: {(r.stderr or r.stdout).strip()[:600]}")
|
|
return json.loads(r.stdout) if r.stdout.strip() else None
|
|
|
|
def body(kind): return open(os.path.join(HERE, kind, KEY + ".cs"), encoding="utf-8").read()
|
|
ORIG, FIXED = body("original"), body("fixed")
|
|
META = json.load(open(os.path.join(HERE, "original", KEY + ".meta.json"), encoding="utf-8"))
|
|
|
|
tmpl = next((t for t in cli("template", "list") if t["name"] == TARGET[0] and not t.get("parentTemplateId")), None)
|
|
if tmpl is None:
|
|
print(f"{URL}: no base template {TARGET[0]} — nothing to do for #{GATE}"); sys.exit(0)
|
|
detail = cli("template", "get", "--id", str(tmpl["id"]), "--detail")
|
|
script = next((s for s in detail["scripts"] if s["name"] == TARGET[1] and not s.get("isInherited")), None)
|
|
if script is None:
|
|
print(f"{URL}: template {TARGET[0]} has no own script {TARGET[1]}"); sys.exit(0)
|
|
live = script.get("code") or ""
|
|
state = "ORIGINAL" if live == ORIG else "FIXED" if live == FIXED else "UNKNOWN"
|
|
insts = [i for i in cli("instance", "list") if i["templateId"] == tmpl["id"]]
|
|
print(f"{URL}: {KEY} template={tmpl['id']} script={script['id']} live body = {state}")
|
|
for i in insts:
|
|
d = cli("instance", "diff", "--id", str(i["id"]))
|
|
print(f" instance {i['uniqueName']} (id {i['id']}) stale={d.get('isStale')} deployedAt={d.get('deployedAt')}")
|
|
if MODE == "check":
|
|
sys.exit(0)
|
|
|
|
want, need = (FIXED, "ORIGINAL") if MODE == "apply" else (ORIG, "FIXED")
|
|
if live == want:
|
|
print(" already in the target state — no script update")
|
|
if not any(cli("instance", "diff", "--id", str(i["id"])).get("isStale") for i in insts):
|
|
print(" every instance current — nothing to deploy"); sys.exit(0)
|
|
elif state != need:
|
|
raise SystemExit(f" REFUSED: live body is {state}, expected {need}. Diff it against {HERE} before touching it.")
|
|
else:
|
|
args = ["template", "script", "update", "--id", str(script["id"]), "--name", script["name"],
|
|
"--code", want, "--trigger-type", script["triggerType"]]
|
|
for opt, field in (("--trigger-config", "triggerConfiguration"), ("--parameters", "parameterDefinitions"),
|
|
("--return-def", "returnDefinition"), ("--min-time-between-runs", "minTimeBetweenRuns"),
|
|
("--execution-timeout-seconds", "executionTimeoutSeconds")):
|
|
v = META.get(field)
|
|
if v not in (None, ""):
|
|
args += [opt, str(v)]
|
|
if META.get("isLocked"):
|
|
args.append("--locked")
|
|
cli(*args)
|
|
after = next(s for s in cli("template", "get", "--id", str(tmpl["id"]), "--detail")["scripts"] if s["id"] == script["id"])
|
|
for field in ("parameterDefinitions", "returnDefinition", "triggerConfiguration"):
|
|
if (after.get(field) or None) != (META.get(field) or None):
|
|
raise SystemExit(f" {field} changed by the update — restore it from original/{KEY}.meta.json")
|
|
if (after.get("code") or "") != want:
|
|
raise SystemExit(" body read back does not match the target file")
|
|
print(f" script {script['id']} updated ({MODE}); metadata read back unchanged")
|
|
|
|
if NODEPLOY:
|
|
print(" --no-deploy: instances left stale; deploy them yourself"); sys.exit(0)
|
|
for i in insts:
|
|
r = cli("instance", "deploy", "--id", str(i["id"]))
|
|
print(f" deploy {i['uniqueName']}: {json.dumps(r)[:200]}")
|
|
PY
|