Skip to content

Commit be8be28

Browse files
committed
feat: Deliver the deployed API version to an HTTP response header
1 parent 49268aa commit be8be28

File tree

3 files changed

+96
-5
lines changed

3 files changed

+96
-5
lines changed

src/common/core/middleware.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from typing import Callable
2+
3+
from django.http import HttpRequest, HttpResponse
4+
5+
from common.core.utils import get_versions_from_manifest
6+
7+
8+
class APIResponseVersionHeaderMiddleware:
9+
"""
10+
Middleware to add the API version to the response headers
11+
"""
12+
13+
def __init__(
14+
self,
15+
get_response: Callable[[HttpRequest], HttpResponse],
16+
) -> None:
17+
self.get_response = get_response
18+
19+
def __call__(self, request: HttpRequest) -> HttpResponse:
20+
response = self.get_response(request)
21+
response.headers["X-Flagsmith-Version"] = self.get_version() or "unknown"
22+
return response
23+
24+
def get_version(self) -> str | None:
25+
"""Obtains the version number from the manifest file"""
26+
manifest_versions = get_versions_from_manifest()
27+
if not manifest_versions:
28+
return None
29+
return manifest_versions.get(".")

src/common/core/utils.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def has_email_provider() -> bool:
5555

5656

5757
def get_version_info() -> VersionInfo:
58-
"""Reads the version info baked into src folder of the docker container"""
58+
"""Returns the version information for the current deployment"""
5959
_is_saas = is_saas()
6060
version_json: VersionInfo = {
6161
"ci_commit_sha": get_file_contents("./CI_COMMIT_SHA") or UNKNOWN,
@@ -66,10 +66,8 @@ def get_version_info() -> VersionInfo:
6666
"self_hosted_data": None,
6767
}
6868

69-
manifest_versions_content = get_file_contents(VERSIONS_INFO_FILE_LOCATION)
70-
71-
if manifest_versions_content:
72-
manifest_versions = json.loads(manifest_versions_content)
69+
manifest_versions = get_versions_from_manifest()
70+
if manifest_versions:
7371
version_json["package_versions"] = manifest_versions
7472
version_json["image_tag"] = manifest_versions["."]
7573

@@ -84,6 +82,17 @@ def get_version_info() -> VersionInfo:
8482
return version_json
8583

8684

85+
@lru_cache()
86+
def get_versions_from_manifest() -> str | None:
87+
"""Reads the version info baked into the Docker container"""
88+
raw_content = get_file_contents(VERSIONS_INFO_FILE_LOCATION)
89+
if not raw_content:
90+
return None
91+
92+
manifest = json.loads(raw_content)
93+
return manifest
94+
95+
8796
@lru_cache()
8897
def get_file_contents(file_path: str) -> str | None:
8998
"""Attempts to read a file from the filesystem and return the contents"""
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pytest
2+
from pytest_mock import MockerFixture
3+
4+
from common.core import middleware as middleware_module
5+
6+
7+
def test_APIResponseVersionHeaderMiddleware__valid_version_info___adds_version_header(
8+
mocker: MockerFixture,
9+
) -> None:
10+
# Given
11+
request = mocker.Mock()
12+
response = mocker.Mock(headers={})
13+
get_response = lambda r: response
14+
middleware = middleware_module.APIResponseVersionHeaderMiddleware(get_response)
15+
get_versions_from_manifest = mocker.patch.object(
16+
middleware_module, "get_versions_from_manifest",
17+
return_value={".": "v1.2.3"},
18+
)
19+
20+
# When
21+
result = middleware(request)
22+
23+
# Then
24+
assert result == response
25+
assert response.headers["X-Flagsmith-Version"] == "v1.2.3"
26+
assert get_versions_from_manifest.call_args_list == [mocker.call()]
27+
28+
29+
@pytest.mark.parametrize("version_info", [
30+
{},
31+
None,
32+
])
33+
def test_APIResponseVersionHeaderMiddleware__invalid_version_info___adds_unknown_header(
34+
mocker: MockerFixture,
35+
version_info: dict[str, str] | None,
36+
) -> None:
37+
# Given
38+
request = mocker.Mock()
39+
response = mocker.Mock(headers={})
40+
get_response = lambda r: response
41+
middleware = middleware_module.APIResponseVersionHeaderMiddleware(get_response)
42+
get_versions_from_manifest = mocker.patch.object(
43+
middleware_module, "get_versions_from_manifest",
44+
return_value=version_info,
45+
)
46+
47+
# When
48+
result = middleware(request)
49+
50+
# Then
51+
assert result == response
52+
assert response.headers["X-Flagsmith-Version"] == "unknown"
53+
assert get_versions_from_manifest.call_args_list == [mocker.call()]

0 commit comments

Comments
 (0)