Skip to content
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: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,6 @@ Note that the `psutil` dependency is **optional** and is only required if you wa
Pyctuator leverages Python's builtin `logging` framework and allows controlling log levels at runtime.

Note that in order to control uvicorn's log level, you need to provide a logger object when instantiating it. For example:


```python
myFastAPIServer = Server(
config=Config(
Expand All @@ -253,6 +251,22 @@ myFastAPIServer = Server(
)
```

### Spring Boot Admin Using Basic Authentication
Pyctuator supports registration with Spring Boot Admin that requires basic authentications. The credentials are provided when initializing the Pyctuator instance as follows:
```python
# NOTE: Never include secrets in your code !!!
auth = Auth(os.getenv("sba-username"), os.getenv("sba-password"))

Pyctuator(
app,
"Flask Pyctuator",
"http://localhost:5000",
f"http://localhost:5000/pyctuator",
registration_url=f"http://spring-boot-admin:8082/instances",
registration_auth=auth,
)
```

## Full blown examples
The `examples` folder contains full blown Python projects that are built using [Poetry](https://python-poetry.org/).

Expand Down
2 changes: 1 addition & 1 deletion examples/Advanced/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ python = "^3.7"
psutil = { version = "^5.6" }
fastapi = { version = "^0.41.0" }
uvicorn = { version = "^0.9.0" }
pyctuator = { version = "^0.9" }
pyctuator = { version = "^0.10" }
sqlalchemy = { version = "^1.3" }
PyMySQL = { version = "^0.9.3" }
cryptography = { version = "^2.8" }
Expand Down
2 changes: 1 addition & 1 deletion examples/FastAPI/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ python = "^3.7"
psutil = { version = "^5.6" }
fastapi = { version = "^0.41.0" }
uvicorn = { version = "^0.9.0" }
pyctuator = { version = "^0.9" }
pyctuator = { version = "^0.10" }

[build-system]
requires = ["poetry>=0.12"]
Expand Down
2 changes: 1 addition & 1 deletion examples/Flask/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [
python = "^3.7"
psutil = { version = "^5.6" }
flask = { version = "^1.1" }
pyctuator = { version = "^0.9" }
pyctuator = { version = "^0.10" }

[build-system]
requires = ["poetry>=0.12"]
Expand Down
13 changes: 13 additions & 0 deletions pyctuator/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from dataclasses import dataclass
from typing import Optional


@dataclass
class Auth:
pass


@dataclass
class BasicAuth(Auth):
username: str
password: Optional[str]
30 changes: 26 additions & 4 deletions pyctuator/impl/spring_boot_admin_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import logging
import threading
import urllib.parse
from base64 import b64encode
from datetime import datetime

from http.client import HTTPConnection
from typing import Optional
from typing import Optional, Dict

from pyctuator.auth import Auth, BasicAuth


# pylint: disable=too-many-instance-attributes
Expand All @@ -15,13 +18,15 @@ class BootAdminRegistrationHandler:
def __init__(
self,
registration_url: str,
registration_auth: Optional[Auth],
application_name: str,
pyctuator_base_url: str,
start_time: datetime,
service_url: str,
registration_interval_sec: int,
) -> None:
self.registration_url = registration_url
self.registration_auth = registration_auth
self.application_name = application_name
self.pyctuator_base_url = pyctuator_base_url
self.start_time = start_time
Expand Down Expand Up @@ -56,17 +61,22 @@ def _register_with_admin_server(self) -> None:
"metadata": {"startup": self.start_time.isoformat()}
}

logging.debug("Trying to post registration data to %s: %s", self.registration_url, registration_data)
logging.debug("Trying to post registration data to %s: %s",
self.registration_url, registration_data)

conn: Optional[HTTPConnection] = None
try:
headers = {"Content-type": "application/json"}
self.authenticate(headers)

reg_url_split = urllib.parse.urlsplit(self.registration_url)
conn = http.client.HTTPConnection(reg_url_split.hostname, reg_url_split.port)
conn.request(
"POST",
reg_url_split.path,
body=json.dumps(registration_data),
headers={"Content-type": "application/json"})
headers=headers,
)
response = conn.getresponse()

if response.status < 200 or response.status >= 300:
Expand All @@ -89,6 +99,9 @@ def deregister_from_admin_server(self) -> None:
if self.instance_id is None:
return

headers = {}
self.authenticate(headers)

deregistration_url = f"{self.registration_url}/{self.instance_id}"
logging.info("Deregistering from %s", deregistration_url)

Expand All @@ -98,7 +111,9 @@ def deregister_from_admin_server(self) -> None:
conn = http.client.HTTPConnection(reg_url_split.hostname, reg_url_split.port)
conn.request(
"DELETE",
reg_url_split.path)
reg_url_split.path,
headers=headers,
)
response = conn.getresponse()

if response.status < 200 or response.status >= 300:
Expand All @@ -111,6 +126,13 @@ def deregister_from_admin_server(self) -> None:
if conn:
conn.close()

def authenticate(self, headers: Dict) -> None:
if isinstance(self.registration_auth, BasicAuth):
password = self.registration_auth.password if self.registration_auth.password else ""
authorization_string = self.registration_auth.username + ":" + password
encoded_authorization: str = b64encode(bytes(authorization_string, "utf-8")).decode("ascii")
headers["Authorization"] = f"Basic {encoded_authorization}"

def start(self) -> None:
logging.info("Starting recurring registration of %s with %s",
self.pyctuator_base_url, self.registration_url)
Expand Down
4 changes: 4 additions & 0 deletions pyctuator/pyctuator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# For example, if the webapp is a Flask webapp, we do not want to import FastAPI, and vice versa.
# To do that, all imports are in conditional branches after detecting which frameworks are installed.
# DO NOT add any web-framework-dependent imports to the global scope.
from pyctuator.auth import Auth
from pyctuator.environment.custom_environment_provider import CustomEnvironmentProvider
from pyctuator.environment.os_env_variables_impl import OsEnvironmentVariableProvider
from pyctuator.health.diskspace_health_impl import DiskSpaceHealthProvider
Expand All @@ -30,6 +31,7 @@ def __init__(
app_url: str,
pyctuator_endpoint_url: str,
registration_url: Optional[str],
registration_auth: Optional[Auth] = None,
app_description: Optional[str] = None,
registration_interval_sec: int = 10,
free_disk_space_down_threshold_bytes: int = 1024 * 1024 * 100,
Expand Down Expand Up @@ -59,6 +61,7 @@ def __init__(
registering the application with spring-boot-admin, must be accessible from spring-boot-admin server (i.e. don't
use http://localhost:8080/... unless spring-boot-admin is running on the same host as the monitored application)
:param registration_url: the spring-boot-admin endpoint to which registration requests must be posted
:param registration_auth: optional authentication details to use when registering with spring-boot-admin
:param registration_interval_sec: how often pyctuator will renew its registration with spring-boot-admin
:param free_disk_space_down_threshold_bytes: amount of free space in bytes in "./" (the application's current
working directory) below which the built-in disk-space health-indicator will fail
Expand Down Expand Up @@ -100,6 +103,7 @@ def __init__(
if registration_url is not None:
self.boot_admin_registration_handler = BootAdminRegistrationHandler(
registration_url,
registration_auth,
app_name,
self.pyctuator_impl.pyctuator_endpoint_url,
start_time,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyctuator"
version = "0.9"
version = "0.10"
description = "A Python implementation of the Spring Actuator API for popular web frameworks"
authors = [
"Michael Yakobi <michael.yakobi@solaredge.com>",
Expand Down