Skip to content

Commit a09aa78

Browse files
author
michaelyaakoby
authored
Merge pull request #9 from SolarEdgeTech/sba-basic-auth
Pyctuator should be able to register with SBA that's using basic-auth
2 parents 6da859a + 5e81e09 commit a09aa78

File tree

8 files changed

+63
-10
lines changed

8 files changed

+63
-10
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,6 @@ Note that the `psutil` dependency is **optional** and is only required if you wa
241241
Pyctuator leverages Python's builtin `logging` framework and allows controlling log levels at runtime.
242242

243243
Note that in order to control uvicorn's log level, you need to provide a logger object when instantiating it. For example:
244-
245-
246244
```python
247245
myFastAPIServer = Server(
248246
config=Config(
@@ -253,6 +251,22 @@ myFastAPIServer = Server(
253251
)
254252
```
255253

254+
### Spring Boot Admin Using Basic Authentication
255+
Pyctuator supports registration with Spring Boot Admin that requires basic authentications. The credentials are provided when initializing the Pyctuator instance as follows:
256+
```python
257+
# NOTE: Never include secrets in your code !!!
258+
auth = Auth(os.getenv("sba-username"), os.getenv("sba-password"))
259+
260+
Pyctuator(
261+
app,
262+
"Flask Pyctuator",
263+
"http://localhost:5000",
264+
f"http://localhost:5000/pyctuator",
265+
registration_url=f"http://spring-boot-admin:8082/instances",
266+
registration_auth=auth,
267+
)
268+
```
269+
256270
## Full blown examples
257271
The `examples` folder contains full blown Python projects that are built using [Poetry](https://python-poetry.org/).
258272

examples/Advanced/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ python = "^3.7"
1111
psutil = { version = "^5.6" }
1212
fastapi = { version = "^0.41.0" }
1313
uvicorn = { version = "^0.9.0" }
14-
pyctuator = { version = "^0.9" }
14+
pyctuator = { version = "^0.10" }
1515
sqlalchemy = { version = "^1.3" }
1616
PyMySQL = { version = "^0.9.3" }
1717
cryptography = { version = "^2.8" }

examples/FastAPI/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ python = "^3.7"
1111
psutil = { version = "^5.6" }
1212
fastapi = { version = "^0.41.0" }
1313
uvicorn = { version = "^0.9.0" }
14-
pyctuator = { version = "^0.9" }
14+
pyctuator = { version = "^0.10" }
1515

1616
[build-system]
1717
requires = ["poetry>=0.12"]

examples/Flask/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ authors = [
1010
python = "^3.7"
1111
psutil = { version = "^5.6" }
1212
flask = { version = "^1.1" }
13-
pyctuator = { version = "^0.9" }
13+
pyctuator = { version = "^0.10" }
1414

1515
[build-system]
1616
requires = ["poetry>=0.12"]

pyctuator/auth.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from dataclasses import dataclass
2+
from typing import Optional
3+
4+
5+
@dataclass
6+
class Auth:
7+
pass
8+
9+
10+
@dataclass
11+
class BasicAuth(Auth):
12+
username: str
13+
password: Optional[str]

pyctuator/impl/spring_boot_admin_registration.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import logging
44
import threading
55
import urllib.parse
6+
from base64 import b64encode
67
from datetime import datetime
78

89
from http.client import HTTPConnection
9-
from typing import Optional
10+
from typing import Optional, Dict
11+
12+
from pyctuator.auth import Auth, BasicAuth
1013

1114

1215
# pylint: disable=too-many-instance-attributes
@@ -15,13 +18,15 @@ class BootAdminRegistrationHandler:
1518
def __init__(
1619
self,
1720
registration_url: str,
21+
registration_auth: Optional[Auth],
1822
application_name: str,
1923
pyctuator_base_url: str,
2024
start_time: datetime,
2125
service_url: str,
2226
registration_interval_sec: int,
2327
) -> None:
2428
self.registration_url = registration_url
29+
self.registration_auth = registration_auth
2530
self.application_name = application_name
2631
self.pyctuator_base_url = pyctuator_base_url
2732
self.start_time = start_time
@@ -56,17 +61,22 @@ def _register_with_admin_server(self) -> None:
5661
"metadata": {"startup": self.start_time.isoformat()}
5762
}
5863

59-
logging.debug("Trying to post registration data to %s: %s", self.registration_url, registration_data)
64+
logging.debug("Trying to post registration data to %s: %s",
65+
self.registration_url, registration_data)
6066

6167
conn: Optional[HTTPConnection] = None
6268
try:
69+
headers = {"Content-type": "application/json"}
70+
self.authenticate(headers)
71+
6372
reg_url_split = urllib.parse.urlsplit(self.registration_url)
6473
conn = http.client.HTTPConnection(reg_url_split.hostname, reg_url_split.port)
6574
conn.request(
6675
"POST",
6776
reg_url_split.path,
6877
body=json.dumps(registration_data),
69-
headers={"Content-type": "application/json"})
78+
headers=headers,
79+
)
7080
response = conn.getresponse()
7181

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

102+
headers = {}
103+
self.authenticate(headers)
104+
92105
deregistration_url = f"{self.registration_url}/{self.instance_id}"
93106
logging.info("Deregistering from %s", deregistration_url)
94107

@@ -98,7 +111,9 @@ def deregister_from_admin_server(self) -> None:
98111
conn = http.client.HTTPConnection(reg_url_split.hostname, reg_url_split.port)
99112
conn.request(
100113
"DELETE",
101-
reg_url_split.path)
114+
reg_url_split.path,
115+
headers=headers,
116+
)
102117
response = conn.getresponse()
103118

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

129+
def authenticate(self, headers: Dict) -> None:
130+
if isinstance(self.registration_auth, BasicAuth):
131+
password = self.registration_auth.password if self.registration_auth.password else ""
132+
authorization_string = self.registration_auth.username + ":" + password
133+
encoded_authorization: str = b64encode(bytes(authorization_string, "utf-8")).decode("ascii")
134+
headers["Authorization"] = f"Basic {encoded_authorization}"
135+
114136
def start(self) -> None:
115137
logging.info("Starting recurring registration of %s with %s",
116138
self.pyctuator_base_url, self.registration_url)

pyctuator/pyctuator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# For example, if the webapp is a Flask webapp, we do not want to import FastAPI, and vice versa.
1010
# To do that, all imports are in conditional branches after detecting which frameworks are installed.
1111
# DO NOT add any web-framework-dependent imports to the global scope.
12+
from pyctuator.auth import Auth
1213
from pyctuator.environment.custom_environment_provider import CustomEnvironmentProvider
1314
from pyctuator.environment.os_env_variables_impl import OsEnvironmentVariableProvider
1415
from pyctuator.health.diskspace_health_impl import DiskSpaceHealthProvider
@@ -30,6 +31,7 @@ def __init__(
3031
app_url: str,
3132
pyctuator_endpoint_url: str,
3233
registration_url: Optional[str],
34+
registration_auth: Optional[Auth] = None,
3335
app_description: Optional[str] = None,
3436
registration_interval_sec: int = 10,
3537
free_disk_space_down_threshold_bytes: int = 1024 * 1024 * 100,
@@ -59,6 +61,7 @@ def __init__(
5961
registering the application with spring-boot-admin, must be accessible from spring-boot-admin server (i.e. don't
6062
use http://localhost:8080/... unless spring-boot-admin is running on the same host as the monitored application)
6163
:param registration_url: the spring-boot-admin endpoint to which registration requests must be posted
64+
:param registration_auth: optional authentication details to use when registering with spring-boot-admin
6265
:param registration_interval_sec: how often pyctuator will renew its registration with spring-boot-admin
6366
:param free_disk_space_down_threshold_bytes: amount of free space in bytes in "./" (the application's current
6467
working directory) below which the built-in disk-space health-indicator will fail
@@ -100,6 +103,7 @@ def __init__(
100103
if registration_url is not None:
101104
self.boot_admin_registration_handler = BootAdminRegistrationHandler(
102105
registration_url,
106+
registration_auth,
103107
app_name,
104108
self.pyctuator_impl.pyctuator_endpoint_url,
105109
start_time,

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pyctuator"
3-
version = "0.9"
3+
version = "0.10"
44
description = "A Python implementation of the Spring Actuator API for popular web frameworks"
55
authors = [
66
"Michael Yakobi <[email protected]>",

0 commit comments

Comments
 (0)