Skip to content

Commit

Permalink
Merge branch 'feature/listen_fds', release v. 0.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
miccoli committed Aug 22, 2023
2 parents b95eb89 + d94cca8 commit 7af59fa
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 20 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Build package, run test matrix, and publish on PyPi

env:
BUILD_PYTHON_VERSION: "3.7"
BUILD_PYTHON_VERSION: "3.8"

on: [push]

Expand Down Expand Up @@ -37,7 +37,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v3
Expand All @@ -54,12 +54,14 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install pytest
pip install mypy
- name: Install built packages
run: |
pip install -U dist/*.whl
- name: Test with pytest
run: |
pytest
run: pytest
- name: Test with mypy
run: mypy --install-types --non-interactive src/trick17 tests

pypi-publish:
needs: test
Expand Down
4 changes: 1 addition & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
default_language_version:
python: python3.11
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
Expand All @@ -15,6 +13,6 @@ repos:
hooks:
- id: black
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.284'
rev: 'v0.0.285'
hooks:
- id: ruff
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pip install trick17

- `daemon.booted()` returns `True` if system was booted by systemd.
- `daemon.notify(state)` sends a notification to systemd.
- `listen_fds()` returns an iterator over (fd, name) tuples in case of socket activation,
see [systemd.socket](https://www.freedesktop.org/software/systemd/man/systemd.socket.html)

### trick17.journal

Expand Down
18 changes: 6 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "trick17"
dynamic = ["version"]
description = "Systemd utility functions in pure python"
readme = "README.md"
requires-python = ">=3.7"
requires-python = ">=3.8"
license = "MIT"
keywords = [
"systemd",
Expand All @@ -20,7 +20,6 @@ classifiers = [
"Intended Audience :: Developers",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
Expand All @@ -40,6 +39,7 @@ path = "src/trick17/__about__.py"
[tool.hatch.envs.default]
dependencies = [
"coverage[toml]>=6.5",
"mypy>=1.0.0",
"pytest",
]
[tool.hatch.envs.default.scripts]
Expand All @@ -53,20 +53,18 @@ cov = [
"test-cov",
"cov-report",
]
typing = "mypy --install-types --non-interactive {args:src/trick17 tests}"

[[tool.hatch.envs.all.matrix]]
python = ["3.7", "3.8", "3.9", "3.10", "3.11"]
python = ["3.8", "3.9", "3.10", "3.11"]

[tool.hatch.envs.lint]
detached = true
dependencies = [
"black>=23.1.0",
"mypy>=1.0.0",
"ruff>=0.0.243",
"pytest",
]
[tool.hatch.envs.lint.scripts]
typing = "mypy --install-types --non-interactive {args:src/trick17 tests}"
style = [
"ruff {args:.}",
"black --check --diff {args:.}",
Expand All @@ -76,17 +74,13 @@ fmt = [
"ruff --fix {args:.}",
"style",
]
all = [
"style",
"typing",
]

[tool.black]
target-version = ["py37"]
target-version = ["py38"]
line-length = 84

[tool.ruff]
target-version = "py37"
target-version = "py38"
line-length = 84
select = [
"A",
Expand Down
2 changes: 1 addition & 1 deletion src/trick17/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023-present Stefano Miccoli <[email protected]>
#
# SPDX-License-Identifier: MIT
__version__ = "0.0.2"
__version__ = "0.0.3"
4 changes: 4 additions & 0 deletions src/trick17/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
# systemd notable paths
SD_BOOTED_PATH = "/run/systemd/system"
SD_JOURNAL_SOCKET_PATH = "/run/systemd/journal/socket"
SD_LISTEN_FDS_START = 3

# environmet variables possibly set by systemd
SD_JOURNAL_STREAM_ENV = "JOURNAL_STREAM"
SD_NOTIFY_SOCKET_ENV = "NOTIFY_SOCKET"
SD_LISTEN_FDS_PID_ENV = "LISTEN_PID"
SD_LISTEN_FDS_ENV = "LISTEN_FDS"
SD_LISTEN_FDS_NAMES_ENV = "LISTEN_FDNAMES"
49 changes: 49 additions & 0 deletions src/trick17/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import os
from pathlib import Path
from typing import Iterator, List, Tuple

import trick17
from trick17 import util
Expand All @@ -16,6 +17,54 @@ def booted() -> bool:
return Path(trick17.SD_BOOTED_PATH).is_dir()


def listen_fds() -> Iterator[Tuple[int, str]]:
"""listen_fds() returns an iterator over (fd, name) tuples, where
- fd is an open file descriptor intialized by systemd socket-activation
- name is an optional name, '' if undefined.
"""

# check pid
if trick17.SD_LISTEN_FDS_PID_ENV not in os.environ:
return iter(())
try:
pid = int(os.environ[trick17.SD_LISTEN_FDS_PID_ENV])
except ValueError as err:
msg = f"Unable to get pid from environment: {err}"
raise RuntimeError(msg) from err
if os.getpid() != pid:
return iter(())

# check FDS
nfds: int
try:
nfds = int(os.environ[trick17.SD_LISTEN_FDS_ENV])
except KeyError as err:
msg = f"Unable to get number of fd's from environment: {err}"
raise RuntimeError(msg) from err
except ValueError as err:
msg = f"Parsing of fd's from environment failed: {err}"
raise RuntimeError(msg) from err
if nfds < 1:
msg = f"Invalid number of fd's in environment: {nfds:d}"
raise RuntimeError(msg)
fds = range(trick17.SD_LISTEN_FDS_START, trick17.SD_LISTEN_FDS_START + nfds)
assert len(fds) == nfds

# check names
names: List[str]
if trick17.SD_LISTEN_FDS_NAMES_ENV not in os.environ:
names = [""] * nfds
else:
names = os.environ[trick17.SD_LISTEN_FDS_NAMES_ENV].split(os.pathsep)
if len(names) > nfds:
names = names[:nfds]
elif len(names) < nfds:
names.extend("" for _ in range(nfds - len(names)))
assert len(names) == len(fds)

return zip(fds, names)


def notify(state: str) -> bool:
"""notify 'state' to systemd; returns
- True if notification sent to socket,
Expand Down
7 changes: 7 additions & 0 deletions tests/test_daemon.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from trick17 import daemon


Expand All @@ -8,3 +10,8 @@ def test_booted():
def test_notify():
ret = daemon.notify("READY=1")
assert ret is False


def test_listen_fds():
with pytest.raises(StopIteration):
next(daemon.listen_fds())
37 changes: 37 additions & 0 deletions tests/tsock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env python

import logging
import os
import socket
import stat

from trick17 import daemon

LISTEN_FDS_START = 3

logging.basicConfig(level=logging.INFO)
for fd, name in daemon.listen_fds():
fstat = os.fstat(fd)
if not stat.S_ISSOCK(fstat.st_mode):
logging.error("fd %d ('%s') not a socket", fd, name)
continue
with socket.socket(fileno=fd) as sock:
logging.info("fd %d ('%s') bound to '%s'", fd, name, sock.getsockname())
if sock.type == socket.SOCK_STREAM:
while True:
conn, address = sock.accept()
logging.info(" remote '%s'", address)
with conn:
data = conn.recv(4096)
logging.info(" -> %s", data)
if data.startswith(b"STOP"):
break
elif sock.type == socket.SOCK_DGRAM:
while True:
data = sock.recv(4096)
logging.info(" -> %s", data)
if data.startswith(b"STOP"):
break
else:
msg = f"Unknown socket type {sock.type}"
raise NotImplementedError(msg)

0 comments on commit 7af59fa

Please sign in to comment.