mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-16 23:55:13 +00:00
97b2452bd2
The version shown in the UI (backend-sourced via /api/version) could lag the
real release. The canonical version lived at repo-root version.json, but the
backend image is built with context ./backend, so that file did not reach the
container unless a pipeline staged it; the backend then fell back to a hardcoded
constant in main.py that had to be bumped by hand and had drifted (it reported
1.8.4 after 1.8.5 and 1.8.6 shipped).
- version.json moves to backend/version.json (single source of truth), now
committed and co-located with main.py, so COPY . . bakes it into every image
directly - correct version in every deployment, no pipeline staging required.
- backend/main.py reads the co-located file; its in-code fallback is no longer a
real version ("unknown") so it can never silently drift again.
- docker-build.yml reads backend/version.json and drops the staging step;
docker-compose.localtest drops the stale root mount.
- backend/tests/test_version_consistency.py fails the build if main.py hardcodes
a real version, if the canonical file is missing/invalid, or if
frontend/package.json drifts from it.
Bumped to 1.8.7. No functional, schema, API, or agent change. Full suite green.
72 lines
3.2 KiB
Python
72 lines
3.2 KiB
Python
"""v1.8.7: app version is single-source and cannot silently drift.
|
|
|
|
The UI shows the version via GET /api/version, which returns main.py's `_version_info`. That MUST be
|
|
sourced from the one canonical file backend/version.json (co-located with main.py so `COPY . .`
|
|
bakes it into every image, regardless of pipeline). main.py's in-code fallback must NOT be a real
|
|
version, otherwise it drifts when version.json is bumped but the constant is forgotten — exactly
|
|
what left the UI reporting 1.8.4 after 1.8.5/1.8.6 shipped. These checks fail loudly on regression.
|
|
"""
|
|
import ast
|
|
import json
|
|
import os
|
|
import re
|
|
|
|
import pytest
|
|
|
|
_BACKEND = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # backend/
|
|
_VERSION_JSON = os.path.join(_BACKEND, "version.json")
|
|
_MAIN = os.path.join(_BACKEND, "main.py")
|
|
_SEMVER = re.compile(r"^\d+\.\d+\.\d+$")
|
|
|
|
|
|
def test_canonical_version_file_exists_and_valid():
|
|
assert os.path.exists(_VERSION_JSON), "backend/version.json (single source of truth) is missing"
|
|
with open(_VERSION_JSON) as f:
|
|
data = json.load(f)
|
|
assert _SEMVER.match(data.get("version", "")), \
|
|
f"backend/version.json version is not semver: {data.get('version')!r}"
|
|
assert data.get("releaseName"), "backend/version.json must have a releaseName"
|
|
|
|
|
|
def _main_fallback_version_info():
|
|
"""The literal dict assigned to _version_info in main.py (the in-code fallback)."""
|
|
with open(_MAIN) as f:
|
|
tree = ast.parse(f.read())
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Assign) and isinstance(node.value, ast.Dict):
|
|
for t in node.targets:
|
|
if isinstance(t, ast.Name) and t.id == "_version_info":
|
|
return ast.literal_eval(node.value)
|
|
return None
|
|
|
|
|
|
def test_main_has_no_hardcoded_real_version():
|
|
fb = _main_fallback_version_info()
|
|
assert fb is not None, "could not find the _version_info fallback literal in main.py"
|
|
# Must be a neutral marker, never a real version that can drift out of sync.
|
|
assert not _SEMVER.match(str(fb.get("version", ""))), (
|
|
f"main.py hardcodes a real version {fb.get('version')!r}; it must be a neutral marker "
|
|
f"(e.g. 'unknown') so the version stays single-source in backend/version.json"
|
|
)
|
|
|
|
|
|
def test_main_loads_the_canonical_file_first():
|
|
# The first candidate path main.py reads must resolve to the co-located backend/version.json,
|
|
# so the correct version is available in every image (not dependent on CI staging).
|
|
with open(_MAIN) as f:
|
|
src = f.read()
|
|
assert 'os.path.dirname(__file__), "version.json"' in src, \
|
|
"main.py must read version.json co-located with the module (backend/version.json)"
|
|
|
|
|
|
def test_frontend_package_json_matches_when_present():
|
|
# Only meaningful in a full-repo checkout; the backend image build context (./backend) has no frontend/.
|
|
pkg = os.path.join(os.path.dirname(_BACKEND), "frontend", "package.json")
|
|
if not os.path.exists(pkg):
|
|
pytest.skip("frontend/package.json not in this context (e.g. backend-only image build)")
|
|
with open(_VERSION_JSON) as f:
|
|
canonical = json.load(f)["version"]
|
|
with open(pkg) as f:
|
|
fe = json.load(f)["version"]
|
|
assert fe == canonical, f"frontend/package.json {fe!r} != backend/version.json {canonical!r}"
|