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

Expose Kopf's version in user-agent & in logs #777

Merged
merged 1 commit into from
May 14, 2021
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
3 changes: 3 additions & 0 deletions kopf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
MultiProgressStorage,
SmartProgressStorage,
)
from kopf._cogs.helpers.versions import (
version as __version__,
)
from kopf._cogs.structs.bodies import (
RawEventType,
RawEvent,
Expand Down
3 changes: 2 additions & 1 deletion kopf/_cogs/clients/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import aiohttp

from kopf._cogs.clients import errors
from kopf._cogs.helpers import versions
from kopf._cogs.structs import credentials

# Per-operator storage and exchange point for authentication methods.
Expand Down Expand Up @@ -186,7 +187,7 @@ def __init__(
auth = None

# It is a good practice to self-identify a bit.
headers['User-Agent'] = f'kopf/unknown' # TODO: add version someday
headers['User-Agent'] = f'kopf/{versions.version or "unknown"}'

# Generic aiohttp session based on the constructed credentials.
self.session = aiohttp.ClientSession(
Expand Down
25 changes: 25 additions & 0 deletions kopf/_cogs/helpers/versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Detecting the framework's own version.

The codebase does not contain the version directly, as it would require
code changes on every release. Kopf's releases depend on tagging rather
than in-code version bumps (Kopf's authour believes that versions belong
to the versioning system, not to the codebase).

The version is determined only once at startup when the code is loaded.
"""
from typing import Optional

version: Optional[str] = None

try:
import pkg_resources
except ImportError:
pass
else:
try:
name, *_ = __name__.split('.') # usually "kopf", unless renamed/forked.
dist: pkg_resources.Distribution = pkg_resources.get_distribution(name)
version = dist.version
except Exception:
pass # installed as an egg, from git, etc.
2 changes: 2 additions & 0 deletions kopf/_core/reactor/running.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from kopf._cogs.aiokits import aioadapters, aiobindings, aiotasks, aiotoggles, aiovalues
from kopf._cogs.clients import auth
from kopf._cogs.configs import configuration
from kopf._cogs.helpers import versions
from kopf._cogs.structs import credentials, ephemera, references, reviews
from kopf._core.actions import execution, lifecycles
from kopf._core.engines import activities, admission, daemons, indexing, peering, posting, probing
Expand Down Expand Up @@ -488,6 +489,7 @@ async def _startup_cleanup_activities(
Beside calling the startup/cleanup handlers, it performs few operator-scoped
cleanups too (those that cannot be handled by garbage collection).
"""
logger.debug(f"Starting Kopf {versions.version or '(unknown version)'}.")

# Execute the startup activity before any root task starts running (due to readiness flag).
try:
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ def fake_vault(mocker, hostname):
try:
yield vault
finally:
# await vault.close() # TODO: but it runs in a different loop, w/ wrong contextvar.
auth.vault_var.reset(token)

#
Expand Down
38 changes: 38 additions & 0 deletions tests/test_versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import json
from typing import Any, Dict

import pytest

from kopf._cogs.clients.auth import APIContext, reauthenticated_request


def test_package_version():
import kopf
assert hasattr(kopf, '__version__')
assert kopf.__version__ # not empty, not null


@pytest.mark.parametrize('version, useragent', [
('1.2.3', 'kopf/1.2.3'),
('1.2rc', 'kopf/1.2rc'),
(None, 'kopf/unknown'),
])
async def test_http_user_agent_version(
aresponses, hostname, fake_vault, mocker, version, useragent):

mocker.patch('kopf._cogs.helpers.versions.version', version)

@reauthenticated_request
async def get_it(url: str, *, context: APIContext) -> Dict[str, Any]:
response = await context.session.get(url)
return await response.json()

async def responder(request):
return aresponses.Response(
content_type='application/json',
text=json.dumps(dict(request.headers)))

aresponses.add(hostname, '/', 'get', responder)
returned_headers = await get_it(f"http://{hostname}/")
assert returned_headers['User-Agent'] == useragent
await fake_vault.close() # to prevent ResourceWarnings for unclosed connectors