Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup detailed version endpoint #163

Merged
merged 2 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Hidden files / folders
.*

# Temp files
*~

# Readmes
**/*.md

# Tests
tests/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# Mac OS
.DS_Store
28 changes: 28 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,38 @@ jobs:

steps:
- uses: actions/checkout@v3
with:
persist-credentials: false
fetch-depth: 0

- name: Set up QEMU
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.9

- name: Install Poetry
uses: ThePalaceProject/circulation/.github/actions/poetry@main
with:
version: "1.3.2"

- name: Setup Dunamai
run: poetry install --only ci
env:
POETRY_VIRTUALENVS_CREATE: false

- name: Create version file
run: |
echo "__version__ = '$(dunamai from git --style semver)'" >> admin/_version.py
echo "__commit__ = '$(dunamai from git --format {commit} --full-commit)'" >> admin/_version.py
echo "__branch__ = '$(dunamai from git --format {branch})'" >> admin/_version.py


- name: Login to the Docker registry
uses: docker/login-action@v2
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ docs/_build/
.env

.vscode

# Our dynamically generated version file
admin/_version.py
18 changes: 18 additions & 0 deletions admin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# These constants are put into a _version.py file by the
# docker build. If they are present, then we want to import
# them here, so they can be used by the application.

try:
from admin._version import __version__
except (ModuleNotFoundError, ImportError):
__version__ = None

try:
from admin._version import __commit__
except (ModuleNotFoundError, ImportError):
__commit__ = None

try:
from admin._version import __branch__
except (ModuleNotFoundError, ImportError):
__branch__ = None
17 changes: 11 additions & 6 deletions admin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,22 @@ def operational_mode(cls) -> OperationalMode:
)

@classmethod
def _package_name(cls) -> str:
def package_name(cls) -> str:
"""Get the effective package name.

:return: A package name.
:rtype: str
"""
return os.environ.get(cls.ENV_ADMIN_UI_PACKAGE_NAME) or cls.PACKAGE_NAME

@classmethod
def package_version(cls) -> str:
"""Get the effective package version.

:return: Package version.
"""
return os.environ.get(cls.ENV_ADMIN_UI_PACKAGE_VERSION) or cls.PACKAGE_VERSION

@classmethod
def lookup_asset_url(
cls, key: str, *, _operational_mode: OperationalMode = None
Expand Down Expand Up @@ -94,11 +102,8 @@ def package_url(cls, *, _operational_mode: OperationalMode = None) -> str:
:rtype: str
"""
operational_mode = _operational_mode or cls.operational_mode()
version = (
os.environ.get(cls.ENV_ADMIN_UI_PACKAGE_VERSION) or cls.PACKAGE_VERSION
)
template = cls.PACKAGE_TEMPLATES[operational_mode]["package_url"]
url = template.format(name=cls._package_name(), version=version)
url = template.format(name=cls.package_name(), version=cls.package_version())
if not url.endswith("/"):
url += "/"
return url
Expand All @@ -115,7 +120,7 @@ def package_development_directory(cls, *, _base_dir: str = None) -> str:
base_dir = _base_dir or cls.ADMIN_DIRECTORY
return os.path.join(
base_dir,
cls.DEVELOPMENT_MODE_PACKAGE_TEMPLATE.format(name=cls._package_name()),
cls.DEVELOPMENT_MODE_PACKAGE_TEMPLATE.format(name=cls.package_name()),
)

@classmethod
Expand Down
14 changes: 13 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,22 @@ def coverage():
return app.library_registry.coverage_controller.lookup()


@app.route("/version.json")
def application_version():
return app.library_registry.version.version()


# TODO: This route is deprecated and should be removed in a
# future release of the code, it has been left here for
# one release to ease any deployment issues.
@app.route("/heartbeat")
@returns_problem_detail
def hearbeat():
return app.library_registry.heartbeat.heartbeat()
version_info = application_version()
version_info[
"WARNING"
] = "The /heartbeat endpoint is deprecated. Please use /version.json instead."
return version_info


# Adobe Vendor ID implementation
Expand Down
4 changes: 2 additions & 2 deletions controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
UNABLE_TO_NOTIFY,
)
from registrar import LibraryRegistrar
from util.app_server import HeartbeatController, catalog_response
from util.app_server import ApplicationVersionController, catalog_response
from util.http import HTTP
from util.problem_detail import ProblemDetail
from util.string_helpers import base64, random_string
Expand Down Expand Up @@ -73,7 +73,7 @@ def setup_controllers(self, emailer_class=Emailer):
self.validation_controller = ValidationController(self)
self.coverage_controller = CoverageController(self)
self.static_files = StaticFileController(self)
self.heartbeat = HeartbeatController()
self.version = ApplicationVersionController()
vendor_id, node_value, delegates = Configuration.vendor_id(self._db)
if vendor_id:
self.adobe_vendor_id = AdobeVendorIDController(
Expand Down
53 changes: 53 additions & 0 deletions tests/util/test_app_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pytest
from flask import Flask, make_response

import admin
from admin.config import Configuration as AdminUiConfig
from util.app_server import ApplicationVersionController


@pytest.mark.parametrize(
"version,commit,branch,ui_version,ui_package",
[("123", "xyz", "abc", "def", "ghi"), (None, None, None, None, None)],
)
def test_application_version_controller(
version, commit, branch, ui_version, ui_package, monkeypatch
):

# Mock the cm version strings
monkeypatch.setattr(admin, "__version__", version)
monkeypatch.setattr(admin, "__commit__", commit)
monkeypatch.setattr(admin, "__branch__", branch)

# Mock the admin ui version strings
if ui_package:
monkeypatch.setenv(AdminUiConfig.ENV_ADMIN_UI_PACKAGE_NAME, ui_package)
else:
monkeypatch.delenv(AdminUiConfig.ENV_ADMIN_UI_PACKAGE_NAME, raising=False)

if ui_version:
monkeypatch.setenv(AdminUiConfig.ENV_ADMIN_UI_PACKAGE_VERSION, ui_version)
else:
monkeypatch.delenv(AdminUiConfig.ENV_ADMIN_UI_PACKAGE_VERSION, raising=False)

with Flask(__name__).test_request_context("/version.json"):
response = make_response(ApplicationVersionController.version())

assert response.status_code == 200
assert response.headers.get("Content-Type") == "application/json"

assert response.json["version"] == version
assert response.json["commit"] == commit
assert response.json["branch"] == branch

# When the env are not set (None) we use defaults
assert (
response.json["admin_ui"]["package"] == ui_package
if ui_package
else AdminUiConfig.PACKAGE_NAME
)
assert (
response.json["admin_ui"]["version"] == ui_version
if ui_version
else AdminUiConfig.PACKAGE_VERSION
)
18 changes: 15 additions & 3 deletions util/app_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from lxml import etree
from psycopg2 import DatabaseError

import admin
from admin.config import Configuration as AdminUiConfig
from opds import OPDSCatalog
from util.problem_detail import ProblemDetail

Expand Down Expand Up @@ -129,6 +131,16 @@ def handle(self, exception):
return response


class HeartbeatController:
def heartbeat(self):
return make_response("", 200, {"Content-Type": "application/json"})
class ApplicationVersionController:
@staticmethod
def version():
response = {
"version": admin.__version__,
"commit": admin.__commit__,
"branch": admin.__branch__,
"admin_ui": {
"package": AdminUiConfig.package_name(),
"version": AdminUiConfig.package_version(),
},
}
return response