plugins/version_endpoint.py (46 lines of code) (raw):
import json
import re
from pathlib import Path
from airflow.plugins_manager import AirflowPlugin
from flask import Blueprint, jsonify
version_endpoint_bp = Blueprint("version_endpoint", __name__)
# from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
SEM_VER_REGEX = (
r"(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\."
r"(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>"
r"(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
r"(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
)
def get_project_root() -> Path:
"""Reliably give the project root as a Path object."""
return Path(__file__).parent.parent
def parse_airflow_version(dockerfile_content: str) -> str:
version_pattern = rf"^FROM apache\/airflow:((slim-)?{SEM_VER_REGEX})$"
version_regex = re.compile(pattern=version_pattern, flags=re.MULTILINE | re.DOTALL)
return version_regex.search(dockerfile_content).group(1)
def get_airflow_version() -> dict[str, str | None]:
"""Parse Airflow version from Dockerfile and return it as a dict."""
project_root = get_project_root()
dockerfile = project_root / "Dockerfile"
if dockerfile.is_file() and dockerfile.exists():
with open(dockerfile) as file:
content = file.read()
version = parse_airflow_version(dockerfile_content=content)
else:
version = None
return {"version": version}
def get_dockerflow_version() -> dict[str, str | None]:
"""
Parse Dockerflow style version.json file and return it as a dict.
version.json is baked in the Docker image at build time in CI.
"""
project_root = get_project_root()
version_file = project_root / "version.json"
if version_file.is_file() and version_file.exists():
with open(project_root / "version.json") as file:
version = json.load(file)
else:
version = {"build": None, "commit": None, "source": None}
return version
@version_endpoint_bp.route("/__version__", methods=["GET"])
def version_endpoint():
airflow_version = get_airflow_version()
dockerflow_version = get_dockerflow_version()
return jsonify(dockerflow_version | airflow_version), 200
class CustomPlugin(AirflowPlugin):
name = "version_endpoint"
flask_blueprints = (version_endpoint_bp,)