qt/ was declared a render path in step 1 but nothing rendered it: the Qt
deploy step was a plain rsync, so the UI files shipped with literal
placeholders in every PV name.
* deploy.yml renders the deployed qt/ directory with render_subs.py
after the rsync. No --service is needed; qt holds no per-service
token, and a file that acquired one would fail the step rather than
ship the placeholder.
* bin/sls_hla_launch_gui.sh no longer passes an AGEBD_ENV_SUFFIX macro.
No .ui file has referenced $(AGEBD_ENV_SUFFIX) since a06fded, and the
suffix is now baked in at deploy time. This removes the last
hardcoded '-DEV' outside config/environments.yml.
check_ui_files.py is rewritten for the chosen convention. It asserted the
old caQtDM macro, and its regex had stopped matching anything at all once
placeholders appeared in device names -- against the pre-fix Service
Manager it inspected 0 names, where the new check finds 16 violations.
The new rule: every AGEBD- device name carries the suffix token, except
systemd unit names, which are the same in both environments. The token
name is read from the central config so a rename cannot leave this check
enforcing a stale one.
Fixing what that check surfaced, all in A_BD_ServiceManager.ui:
* 16 'IOC=AGEBD-CPCL-<NAME>' caInclude macro values had no suffix. The
included UI uses them as PV prefixes ($(IOC):STATUS, $(IOC):RESTART),
so the dev Service Manager was reading status from, and sending
restarts to, production IOCs.
* Two ssh commands hardcoded sls-vserv-bd-hla01, so the dev GUI's
restart button targeted the production HLA host.
* cli generates those macro values, so the generator emits the token
too and new services are no longer born with the bug.
These were latent while qt/ shipped unrendered placeholders; turning
rendering on would have made them live.
Verified: dev renders -DEV device names and the -dev host, prod renders
byte-identical to the currently deployed prod files, no placeholder
survives in either, and all 24 UI files stay well-formed XML.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
92 lines
2.9 KiB
Python
92 lines
2.9 KiB
Python
"""
|
|
Guard the environment suffix in Qt UI files.
|
|
|
|
qt/ is rendered at deploy time (see config/environments.yml), so every EPICS
|
|
device name in a .ui file must carry the suffix token. A name without it
|
|
resolves to the production device in every environment -- which is how the
|
|
dev Service Manager ended up driving prod IOCs.
|
|
|
|
The rule is: every AGEBD- name is immediately followed by the suffix token.
|
|
The one exception is systemd unit names, which genuinely have no environment
|
|
suffix (the unit file is AGEBD-SERVICE-<NAME>.service in both environments).
|
|
|
|
This replaces an earlier check for the caQtDM macro '$(AGEBD_ENV_SUFFIX)',
|
|
which was abandoned in favour of deploy-time rendering. That check had also
|
|
stopped matching anything once placeholders appeared in the names.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
|
|
from agebd.subs import SubstitutionsConfig
|
|
from agebd.utils import get_git_root
|
|
|
|
REPO_ROOT = get_git_root(__file__)
|
|
QT_DIR = REPO_ROOT / "qt"
|
|
|
|
SUFFIX_TOKEN_NAME = "agebd_env_suffix_upper"
|
|
|
|
#: An EPICS device name belonging to us, e.g. AGEBD-ALH or AGEBD-CPCL-TUNE.
|
|
AGEBD_NAME = re.compile(r"AGEBD-[0-9A-Z][0-9A-Z-]*")
|
|
|
|
#: Systemd unit names are the same in every environment.
|
|
EXEMPT_PREFIXES = ("AGEBD-SERVICE-",)
|
|
|
|
|
|
def suffix_token(config):
|
|
"""
|
|
The token text these files must carry, taken from the central config so
|
|
that renaming the token cannot leave this check enforcing a stale name.
|
|
"""
|
|
if SUFFIX_TOKEN_NAME not in config.tokens:
|
|
raise AssertionError(
|
|
f"Token '{SUFFIX_TOKEN_NAME}' is not declared in "
|
|
f"{config.environments_file}, but qt/ is checked against it."
|
|
)
|
|
return f"@@{SUFFIX_TOKEN_NAME}@@"
|
|
|
|
|
|
def check_ui_file(text, token, filename):
|
|
"""
|
|
Report every AGEBD- device name in ``text`` that lacks ``token``.
|
|
"""
|
|
errors = []
|
|
for match in AGEBD_NAME.finditer(text):
|
|
name = match.group(0)
|
|
if name.startswith(EXEMPT_PREFIXES):
|
|
continue
|
|
if text[match.end() : match.end() + len(token)] == token:
|
|
continue
|
|
line = text.count("\n", 0, match.start()) + 1
|
|
errors.append(
|
|
f"{filename}:{line}: '{name}' is missing '{token}', so it resolves to the "
|
|
f"production device in every environment."
|
|
)
|
|
return errors
|
|
|
|
|
|
def check_ui_files():
|
|
"""
|
|
Raise AssertionError listing every violation, or return silently.
|
|
"""
|
|
config = SubstitutionsConfig.load(REPO_ROOT / "config")
|
|
token = suffix_token(config)
|
|
|
|
errors = []
|
|
for path in sorted(QT_DIR.glob("*.ui")):
|
|
errors.extend(check_ui_file(path.read_text(), token, path.relative_to(REPO_ROOT)))
|
|
|
|
if errors:
|
|
raise AssertionError(
|
|
"Qt UI files missing the environment suffix:\n\n " + "\n ".join(errors)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
check_ui_files()
|
|
except AssertionError as e:
|
|
print(e, file=sys.stderr)
|
|
sys.exit(1)
|
|
print("All AGEBD- device names in qt/ carry the environment suffix.")
|