Customizer API¶
The Customizer API is a FastAPI server that runs the Customizer and exposes endpoints to upload and manage payloads, check the status of payload processing and run maintenance tasks.
Start the server with:
The interactive OpenAPI documentation is available at http://localhost:8000/api once the server is
running. See API Settings for the available configuration options.
Application¶
app
¶
API Implementation for the Customizer to start and control the payload processing.
Functions:
| Name | Description |
|---|---|
lifespan |
Lifespan function for FASTAPI to handle the startup and shutdown process. |
run_api |
Start the FastAPI Webserver. |
lifespan
async
¶
Lifespan function for FASTAPI to handle the startup and shutdown process.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
FastAPI
|
The FastAPI application. |
required |
Source code in packages/pyxecm/src/pyxecm_api/app.py
run_api
¶
Start the FastAPI Webserver.
Source code in packages/pyxecm/src/pyxecm_api/app.py
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | |
Authentication¶
router
¶
Utility library to handle the authentication with OTDS.
Functions:
| Name | Description |
|---|---|
login |
Login using OTDS and return a token. |
read_users_me |
Get the current user. |
login
¶
Login using OTDS and return a token.
Source code in packages/pyxecm/src/pyxecm_api/auth/router.py
read_users_me
async
¶
read_users_me(
current_user: Annotated[
User, Depends(get_current_user)
],
) -> JSONResponse
Get the current user.
current_user (User): The current user.
Source code in packages/pyxecm/src/pyxecm_api/auth/router.py
models
¶
functions
¶
Utility library to handle the authentication with OTDS.
Functions:
| Name | Description |
|---|---|
get_groups |
Get the groups of the user. |
get_current_user |
Get the current user from OTDS and verify it. |
get_authorized_user |
Check if the user is authorized (member of the Group otadmin@otds.admin or should be a tenantAdmin user). |
get_otcsticket |
Check if the user is authorized (member of the Group otadmin@otds.admin). |
get_groups
¶
Get the groups of the user.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
response
|
dict
|
description |
required |
token
|
str
|
description |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list
|
description |
Source code in packages/pyxecm/src/pyxecm_api/auth/functions.py
get_current_user
¶
get_current_user(
token: Annotated[str, Depends(oauth2_scheme)],
api_key: Annotated[str, Depends(apikey_header)],
) -> User
Get the current user from OTDS and verify it.
Source code in packages/pyxecm/src/pyxecm_api/auth/functions.py
get_authorized_user
async
¶
get_authorized_user(
current_user: Annotated[
User, Depends(get_current_user)
],
) -> User
Common¶
router
¶
API Implementation for the Customizer to start and control the payload processing.
Functions:
| Name | Description |
|---|---|
redirect_to_api |
Redirect from / to /api. |
get_status |
Get the status of the Customizer. |
shutdown |
Endpoint to end the application. |
list_browser_automation_files |
List all browser automation files. |
get_browser_automation_file |
Download the logfile for a specific payload. |
redirect_to_api
async
¶
Redirect from / to /api.
Returns:
| Type | Description |
|---|---|
RedirectResponse
|
None |
get_status
async
¶
get_status() -> CustomizerStatus
Get the status of the Customizer.
Source code in packages/pyxecm/src/pyxecm_api/common/router.py
shutdown
¶
shutdown(
user: Annotated[User, Depends(get_authorized_user)],
) -> JSONResponse
Endpoint to end the application.
Source code in packages/pyxecm/src/pyxecm_api/common/router.py
list_browser_automation_files
¶
list_browser_automation_files(
user: Annotated[User, Depends(get_authorized_user)],
) -> JSONResponse
List all browser automation files.
Source code in packages/pyxecm/src/pyxecm_api/common/router.py
get_browser_automation_file
¶
get_browser_automation_file(
user: Annotated[User, Depends(get_authorized_user)],
file: Annotated[str, Query(description="File name")],
) -> FileResponse
Download the logfile for a specific payload.
Source code in packages/pyxecm/src/pyxecm_api/common/router.py
models
¶
Define common base Models.
Classes:
| Name | Description |
|---|---|
CustomizerStatus |
Define Model for Customizer Status. |
CustomizerStatus
¶
Bases: BaseModel
Define Model for Customizer Status.
metrics
¶
Metrics for payload logs.
Functions:
| Name | Description |
|---|---|
payload_logs_by_payload |
Metrics for payload logs by payload. |
payload_logs_total |
Metrics for total payload logs messages. |
payload_logs_by_payload
¶
payload_logs_by_payload(
payload_list: PayloadList,
) -> Callable[[Info], None]
Metrics for payload logs by payload.
Source code in packages/pyxecm/src/pyxecm_api/common/metrics.py
payload_logs_total
¶
payload_logs_total(
payload_list: PayloadList,
) -> Callable[[Info], None]
Metrics for total payload logs messages.
Source code in packages/pyxecm/src/pyxecm_api/common/metrics.py
functions
¶
Define common functions.
Functions:
| Name | Description |
|---|---|
get_k8s_object |
Get an instance of a K8s object. |
get_otcs_object |
Get an instance of a Content Server (OTCS) object. |
get_otcs_object_from_otcsticket |
Get an instance of a Content Server (OTCS) object. |
get_settings |
Get the API Settings object. |
get_otcs_logs_lock |
Get the Logs LOCK dict. |
list_files_in_directory |
Recursively list files in a directory and return a nested JSON structure with URLs. |
get_k8s_object
¶
get_k8s_object() -> K8s
get_otcs_object
¶
get_otcs_object() -> OTCS
Get an instance of a Content Server (OTCS) object.
Returns:
| Name | Type | Description |
|---|---|---|
OTCS |
OTCS
|
Return a new OTCS object. |
Source code in packages/pyxecm/src/pyxecm_api/common/functions.py
get_otcs_object_from_otcsticket
¶
get_otcs_object_from_otcsticket(
otcs_ticket: Annotated[str, Depends(get_otcsticket)],
) -> OTCS
Get an instance of a Content Server (OTCS) object.
Returns:
| Name | Type | Description |
|---|---|---|
OTCS |
OTCS
|
Return an OTCS object. |
Source code in packages/pyxecm/src/pyxecm_api/common/functions.py
get_settings
¶
Get the API Settings object.
Returns:
| Name | Type | Description |
|---|---|---|
CustomizerPISettings |
CustomizerAPISettings
|
Returns the API Settings. |
get_otcs_logs_lock
¶
Get the Logs LOCK dict.
Returns:
| Type | Description |
|---|---|
dict
|
The dict with all LOCKS for the logs |
list_files_in_directory
¶
Recursively list files in a directory and return a nested JSON structure with URLs.
Source code in packages/pyxecm/src/pyxecm_api/common/functions.py
Maintenance¶
router
¶
Define router for v1_maintenance.
Functions:
| Name | Description |
|---|---|
status |
Return status of maintenance mode. |
set_maintenance_mode_options |
Configure the Maintenance Mode and set options. |
status
async
¶
status(
user: Annotated[User, Depends(get_authorized_user)],
k8s_object: Annotated[K8s, Depends(get_k8s_object)],
) -> MaintenanceModel
Return status of maintenance mode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
User
|
Added to enforce authentication requirement |
required |
k8s_object
|
K8s
|
K8s object instance of pyxecm K8s class |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
MaintenanceModel
|
Details of maintenance mode. |
Source code in packages/pyxecm/src/pyxecm_api/v1_maintenance/router.py
set_maintenance_mode_options
async
¶
set_maintenance_mode_options(
user: Annotated[User, Depends(get_authorized_user)],
k8s_object: Annotated[K8s, Depends(get_k8s_object)],
config: Annotated[MaintenanceModel, Form()],
) -> MaintenanceModel
Configure the Maintenance Mode and set options.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
User
|
Added to enforce authentication requirement |
required |
k8s_object
|
K8s
|
K8s object instance of pyxecm K8s class |
required |
config
|
MaintenanceModel
|
instance of the Maintenance Model |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
MaintenanceModel
|
description |
Source code in packages/pyxecm/src/pyxecm_api/v1_maintenance/router.py
models
¶
Define Models for the Maintenance Page Config.
Classes:
| Name | Description |
|---|---|
MaintenanceModel |
Status object of the Maintenance Page. |
MaintenanceModel
¶
Bases: BaseModel
Status object of the Maintenance Page.
functions
¶
Define functions for v1_maintenance.
Functions:
| Name | Description |
|---|---|
get_cshost |
Get the cs_hostname from the environment Variable OTCS_PUBLIC_HOST otherwise read it from the otcs-frontend-configmap. |
get_maintenance_mode_status |
Get the status of maintenance mode by inspecting the ingress object. |
set_maintenance_mode_via_ingress |
Set maintenance mode. |
get_cshost
¶
get_cshost(k8s_object: K8s) -> str
Get the cs_hostname from the environment Variable OTCS_PUBLIC_HOST otherwise read it from the otcs-frontend-configmap.
Source code in packages/pyxecm/src/pyxecm_api/v1_maintenance/functions.py
get_maintenance_mode_status
¶
get_maintenance_mode_status(k8s_object: K8s) -> dict
Get the status of maintenance mode by inspecting the ingress object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k8s_object
|
K8s
|
An instance of the K8s class used to interact with Kubernetes resources. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
A dictionary containing the details of the maintenance mode, including: - enabled (bool): Indicates whether maintenance mode is enabled. - title (str): The title of the maintenance mode message. - text (str): The text of the maintenance mode message. - footer (str): The footer of the maintenance mode message. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
If no ingress object is found to determine the maintenance mode status. |
Source code in packages/pyxecm/src/pyxecm_api/v1_maintenance/functions.py
set_maintenance_mode_via_ingress
¶
set_maintenance_mode_via_ingress(
enabled: bool, k8s_object: K8s
) -> None
Set maintenance mode.
Source code in packages/pyxecm/src/pyxecm_api/v1_maintenance/functions.py
OTCS¶
router
¶
Define router for v1_otcs.
Functions:
| Name | Description |
|---|---|
put_otcs_logs |
Collect the logs from the given OTCS instances. |
post_otcs_log_file |
Upload a file to disk. |
get_otcs_log_files |
List all otcs logs that can be downloaded. |
delete_otcs_log_files |
Delete all otcs log files. |
delete_otcs_log_file |
Delete single OTCS log archive. |
get_otcs_log_file |
Download OTCS log archive. |
put_otcs_logs
async
¶
put_otcs_logs(
user: Annotated[User, Depends(get_authorized_user)],
k8s_object: Annotated[K8s, Depends(get_k8s_object)],
settings: Annotated[
CustomizerAPISettings, Depends(get_settings)
],
otcs_logs_lock: Annotated[
dict[str, Lock], Depends(get_otcs_logs_lock)
],
hosts: Annotated[list[str], Body()],
) -> JSONResponse
Collect the logs from the given OTCS instances.
Source code in packages/pyxecm/src/pyxecm_api/v1_otcs/router.py
post_otcs_log_file
async
¶
post_otcs_log_file(
file: Annotated[UploadFile, File(...)],
settings: Annotated[
CustomizerAPISettings, Depends(get_settings)
],
key: str = "",
) -> JSONResponse
Upload a file to disk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file
|
Annotated[UploadFile, File(...)]
|
File to be uploaded. |
required |
settings
|
Annotated[CustomizerAPISettings, Depends(get_settings)]
|
CustomizerAPISettings. |
required |
key
|
str
|
Key to validate the upload. |
''
|
Returns:
| Name | Type | Description |
|---|---|---|
JSONResponse |
JSONResponse
|
Status of the upload |
Source code in packages/pyxecm/src/pyxecm_api/v1_otcs/router.py
get_otcs_log_files
async
¶
get_otcs_log_files(
user: Annotated[User, Depends(get_authorized_user)],
settings: Annotated[
CustomizerAPISettings, Depends(get_settings)
],
k8s_object: Annotated[K8s, Depends(get_k8s_object)],
otcs_logs_lock: Annotated[
dict[str, Lock], Depends(get_otcs_logs_lock)
],
) -> JSONResponse
List all otcs logs that can be downloaded.
Source code in packages/pyxecm/src/pyxecm_api/v1_otcs/router.py
delete_otcs_log_files
async
¶
delete_otcs_log_files(
user: Annotated[User, Depends(get_authorized_user)],
settings: Annotated[
CustomizerAPISettings, Depends(get_settings)
],
) -> JSONResponse
Delete all otcs log files.
Source code in packages/pyxecm/src/pyxecm_api/v1_otcs/router.py
delete_otcs_log_file
async
¶
delete_otcs_log_file(
user: Annotated[User, Depends(get_authorized_user)],
settings: Annotated[
CustomizerAPISettings, Depends(get_settings)
],
file_name: str,
) -> FileResponse
Delete single OTCS log archive.
Source code in packages/pyxecm/src/pyxecm_api/v1_otcs/router.py
get_otcs_log_file
async
¶
get_otcs_log_file(
user: Annotated[User, Depends(get_authorized_user)],
settings: Annotated[
CustomizerAPISettings, Depends(get_settings)
],
file_name: str,
) -> FileResponse
Download OTCS log archive.
Source code in packages/pyxecm/src/pyxecm_api/v1_otcs/router.py
functions
¶
Define functions for v1_otcs.
Functions:
| Name | Description |
|---|---|
collect_otcs_logs |
Collect the logs for the given OTCS instance. |
collect_otcs_logs
¶
collect_otcs_logs(
host: str,
k8s_object: K8s,
logs_lock: Lock,
settings: CustomizerAPISettings,
) -> None
Collect the logs for the given OTCS instance.
Source code in packages/pyxecm/src/pyxecm_api/v1_otcs/functions.py
Payload¶
router
¶
Define router for v1_payload.
Functions:
| Name | Description |
|---|---|
create_payload_item |
Upload a new payload item. |
get_payload_items |
Get all Payload items. |
get_payload_item |
Get a payload item based on its ID. |
update_payload_item |
Update an existing payload item. |
delete_payload_item |
Delete an existing payload item. |
move_payload_item_up |
Move a payload item up in the list. |
move_payload_item_down |
Move a payload item down in the list. |
get_payload_content |
Get a payload item based on its ID. |
download_payload_content |
Download the payload for a specific payload item. |
download_payload_logfile |
Download the logfile for a specific payload. |
get_payload_logfile |
Get the log file of a payload for the endpoints that read parts of it. |
get_payload_logfile_info |
Describe the logfile of a payload and the progress of its line index. |
get_payload_logfile_lines |
Read a window of lines from the logfile of a payload, optionally filtered. |
stream_logfile |
Stream the logfile and follow changes. |
create_payload_item
async
¶
create_payload_item(
user: Annotated[User, Depends(get_authorized_user)],
settings: Annotated[
CustomizerAPISettings, Depends(get_settings)
],
upload_file: Annotated[UploadFile, File(...)],
name: Annotated[str, Form()] = "",
dependencies: Annotated[
list[int] | list[str] | None, Form()
] = None,
enabled: Annotated[bool, Form()] = True,
loglevel: Annotated[
Literal["DEBUG", "INFO", "WARNING"] | None,
Form(
description="Loglevel for the Payload processing"
),
] = "INFO",
) -> PayloadListItem
Upload a new payload item.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
User
|
The user who is uploading the payload. Defaults to None. |
required |
settings
|
CustomizerAPISettings
|
The settings object. |
required |
upload_file
|
UploadFile
|
The file to upload. Defaults to File(...). |
required |
name
|
str
|
The name of the payload (if not provided we will use the file name). |
''
|
dependencies
|
list of integers
|
List of other payload items this item depends on. |
None
|
enabled
|
bool
|
Flag indicating if the payload is enabled or not. |
True
|
loglevel
|
str
|
The loglevel for the payload processing. Defaults to "INFO". |
'INFO'
|
Raises:
| Type | Description |
|---|---|
HTTPException
|
Raised, if payload list is not initialized. |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
PayloadListItem
|
The HTTP response. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
get_payload_items
async
¶
get_payload_items(
user: Annotated[User, Depends(get_authorized_user)],
) -> PayloadListItems
Get all Payload items.
Raises:
| Type | Description |
|---|---|
HTTPException
|
payload list not initialized |
HTTPException
|
payload list is empty |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
PayloadListItems
|
HTTP response with the result data |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
get_payload_item
async
¶
get_payload_item(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> PayloadListItem
Get a payload item based on its ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
Annotated[User, Depends(get_authorized_user)]
|
Annotated[User, Depends(get_authorized_user)] |
required |
payload_id
|
int
|
payload item ID |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
a payload item with the given ID couldn't be found |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
PayloadListItem
|
HTTP response. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
update_payload_item
async
¶
update_payload_item(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
name: Annotated[str | None, Form()] = None,
dependencies: Annotated[
list[int] | list[str] | None, Form()
] = None,
enabled: Annotated[bool | None, Form()] = None,
status: Annotated[
Literal["planned", "completed"] | None, Form()
] = None,
loglevel: Annotated[
Literal["DEBUG", "INFO", "WARNING"] | None,
Form(
description="Loglevel for the Payload processing"
),
] = None,
customizer_settings: Annotated[
str | None, Form()
] = None,
) -> UpdatedPayloadListItem
Update an existing payload item.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
Optional[User]
|
User performing the update. |
required |
payload_id
|
int
|
ID of the payload to update. |
required |
name
|
Optional[str]
|
Updated name. |
None
|
dependencies
|
Optional[List[int]]
|
Updated list of dependencies. |
None
|
enabled
|
Optional[bool]
|
Updated enabled status. |
None
|
loglevel
|
Optional[str]
|
Updated loglevel. |
None
|
status
|
Optional[str]
|
Updated status. |
None
|
customizer_settings
|
Optional[str]
|
Updated customizer settings. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
UpdatedPayloadListItem
|
HTTP response with the updated payload details. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | |
delete_payload_item
async
¶
delete_payload_item(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> JSONResponse
Delete an existing payload item.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
Optional[User]
|
User performing the update. |
required |
payload_id
|
int
|
The ID of the payload to update. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
JSONResponse
|
response or None |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
move_payload_item_up
async
¶
move_payload_item_up(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> dict
Move a payload item up in the list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
Annotated[User, Depends(get_authorized_user)]
|
Annotated[User, Depends(get_authorized_user)] |
required |
payload_id
|
int
|
payload item ID |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
a payload item with the given ID couldn't be found |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
HTTP response |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
move_payload_item_down
async
¶
move_payload_item_down(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> dict
Move a payload item down in the list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
Annotated[User, Depends(get_authorized_user)]
|
Annotated[User, Depends(get_authorized_user)] |
required |
payload_id
|
int
|
The payload item ID. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
a payload item with the given ID couldn't be found |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
HTTP response |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
get_payload_content
async
¶
get_payload_content(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> dict | None
Get a payload item based on its ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
Annotated[User, Depends(get_authorized_user)]
|
Annotated[User, Depends(get_authorized_user)] |
required |
payload_id
|
int
|
The payload item ID. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
A payload item with the given ID couldn't be found. |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict | None
|
HTTP response. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
download_payload_content
async
¶
download_payload_content(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> FileResponse
Download the payload for a specific payload item.
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
download_payload_logfile
async
¶
download_payload_logfile(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> FileResponse
Download the logfile for a specific payload.
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
get_payload_logfile
¶
Get the log file of a payload for the endpoints that read parts of it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload_id
|
int
|
The payload item ID. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
A payload item with the given ID couldn't be found. |
Returns:
| Type | Description |
|---|---|
tuple[str, bool]
|
tuple[str, bool]: The path of the log file and whether the payload is running, i.e. whether the log is still being written. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
get_payload_logfile_info
async
¶
get_payload_logfile_info(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> PayloadLogInfo
Describe the logfile of a payload and the progress of its line index.
The line index makes it possible to read any part of a log of any size (see
/log/lines). It is built in the background when a log is looked at for the first time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
Annotated[User, Depends(get_authorized_user)]
|
Annotated[User, Depends(get_authorized_user)] |
required |
payload_id
|
int
|
The payload item ID. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
The payload item or its logfile couldn't be found. |
Returns:
| Name | Type | Description |
|---|---|---|
PayloadLogInfo |
PayloadLogInfo
|
Size, number of lines and number of log records per level of the logfile, how far the line index reaches and whether the payload is running. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
get_payload_logfile_lines
async
¶
get_payload_logfile_lines(
request: Request,
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
lines_request: Annotated[
LogLinesRequest, Depends(lines_request_from_query)
],
) -> PayloadLogLines
Read a window of lines from the logfile of a payload, optionally filtered.
The logfile is never read into memory as a whole, so this works for logs of any size.
A request stops after limit lines, after max_bytes, at the end (or the beginning) of
the logfile, or when it has worked for about a second. The response tells which byte range
of the logfile it covers: continue with offset=<end>&direction=forward or with
offset=<start>&direction=backward until eof or bof is true.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The request. The scan stops when its client disconnects. |
required |
user
|
Annotated[User, Depends(get_authorized_user)]
|
Annotated[User, Depends(get_authorized_user)] |
required |
payload_id
|
int
|
The payload item ID. |
required |
lines_request
|
LogLinesRequest
|
What to read: the query parameters offset, line, direction, limit, max_bytes,
levels, q, regex, case, word and count_only (see |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
The payload item or its logfile couldn't be found (404), or a parameter is invalid (400). |
Returns:
| Name | Type | Description |
|---|---|---|
PayloadLogLines |
PayloadLogLines
|
The lines (ascending), the byte range they were taken from and the state of the logfile. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
stream_logfile
async
¶
stream_logfile(
user: Annotated[User, Depends(get_authorized_user)],
payload_id: int,
) -> StreamingResponse
Stream the logfile and follow changes.
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/router.py
models
¶
Define Models for Payload.
Classes:
| Name | Description |
|---|---|
PayloadStats |
Defines PayloadStats Model. |
PayloadListItem |
Defines PayloadListItem Model. |
PayloadListItems |
Defines PayloadListItems Model. |
UpdatedPayloadListItem |
Defines UpdatedPayloadListItem Model. |
PayloadLogInfo |
Defines PayloadLogInfo Model: a payload log file and the progress of its line index. |
PayloadLogLine |
Defines PayloadLogLine Model: one line of a payload log file. |
PayloadLogLines |
Defines PayloadLogLines Model: a window of lines of a payload log file. |
PayloadStats
¶
Bases: BaseModel
Defines PayloadStats Model.
PayloadListItem
¶
Bases: BaseModel
Defines PayloadListItem Model.
PayloadListItems
¶
Bases: BaseModel
Defines PayloadListItems Model.
UpdatedPayloadListItem
¶
Bases: BaseModel
Defines UpdatedPayloadListItem Model.
PayloadLogInfo
¶
Bases: BaseModel
Defines PayloadLogInfo Model: a payload log file and the progress of its line index.
PayloadLogLine
¶
Bases: BaseModel
Defines PayloadLogLine Model: one line of a payload log file.
PayloadLogLines
¶
Bases: BaseModel
Defines PayloadLogLines Model: a window of lines of a payload log file.
functions
¶
API Implementation for the Customizer to start and control the payload processing.
Classes:
| Name | Description |
|---|---|
GrowingFileResponse |
A file download for a file that may be appended to while it is sent. |
Functions:
| Name | Description |
|---|---|
import_payload |
Automatically load payload items from disk of a given directory. |
prepare_dependencies |
Convert the dependencies string to a list of integers. |
tail_log |
Asynchronously follow the log file like |
run_log_read |
Run a function that reads a log file in a worker thread, off the event loop. |
lines_request_from_query |
Read the query parameters of a request for log lines (a dependency of the |
GrowingFileResponse
¶
Bases: FileResponse
A file download for a file that may be appended to while it is sent.
The size of the file is announced before its content is sent. The plain FileResponse
then sends the file up to its end, which is more than announced if the file grew meanwhile,
and the server aborts such a response. This response sends exactly the announced bytes.
Everything else, Range requests included, is the behaviour of FileResponse.
import_payload
¶
import_payload(
payload: str | None = None,
payload_dir: str | None = None,
enabled: bool | None = None,
dependencies: bool | None = None,
) -> None
Automatically load payload items from disk of a given directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
str
|
The name of the payload. |
None
|
payload_dir
|
str
|
The local path. |
None
|
enabled
|
bool
|
Automatically start the processing (True), or only define items (False). Defaults to False. |
None
|
dependencies
|
bool
|
Automatically add dependency on the last payload in the queue |
None
|
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/functions.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
prepare_dependencies
¶
Convert the dependencies string to a list of integers.
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/functions.py
tail_log
async
¶
Asynchronously follow the log file like tail -f.
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/functions.py
run_log_read
async
¶
Run a function that reads a log file in a worker thread, off the event loop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request | None
|
The request the result is for. If given, the function gets a |
required |
function
|
Callable
|
The function to run. |
required |
*args
|
Any
|
The arguments of the function. |
()
|
Returns:
| Name | Type | Description |
|---|---|---|
Any |
Any
|
The result of the function. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/functions.py
lines_request_from_query
¶
lines_request_from_query(
offset: Annotated[
int | None,
Query(
description="Byte offset to start at. It is snapped back to the start of the line containing it."
),
] = None,
line: Annotated[
int | None,
Query(
description="Line number (1-based) to start at. Alternative to the offset."
),
] = None,
direction: Annotated[
str,
Query(
description="'forward' returns the lines from the start position on, 'backward' the lines before it. Without start position, 'forward' starts at the beginning of the logfile and 'backward' at its end."
),
] = "forward",
limit: Annotated[
int,
Query(
description="Maximum number of lines to return (0 to 5000)."
),
] = DEFAULT_LIMIT,
max_bytes: Annotated[
int,
Query(
description="Soft cap on the summed size of the returned lines. At most 4 MiB are used."
),
] = DEFAULT_MAX_BYTES,
levels: Annotated[
str | None,
Query(
description="Comma separated list of the levels to return (DEBUG, INFO, WARNING, ERROR). Lines without a level are always returned. An empty value returns only those."
),
] = None,
q: Annotated[
str | None,
Query(
description="Text to search for. Only matching lines are returned."
),
] = None,
regex: Annotated[
bool,
Query(
description="The search text is a regular expression (Python syntax)."
),
] = False,
case: Annotated[
bool,
Query(
description="Match the case of the search text."
),
] = False,
word: Annotated[
bool, Query(description="Match whole words only.")
] = False,
count_only: Annotated[
bool,
Query(
description="Return no lines but count them. The limit and max_bytes are ignored."
),
] = False,
) -> LogLinesRequest
Read the query parameters of a request for log lines (a dependency of the /lines endpoints).
The payload logs and the pod logs are read with the same parameters (see build_lines_request).
Raises:
| Type | Description |
|---|---|
HTTPException
|
A parameter is invalid (400). |
Returns:
| Name | Type | Description |
|---|---|---|
LogLinesRequest |
LogLinesRequest
|
The request. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/functions.py
log_reader
¶
Windowed reading, level filtering and searching of payload log files of any size.
A payload log can grow to several gigabytes. Nothing in this module reads a whole log into memory: the file is read in chunks, and a small in-memory index per log file makes every byte offset resolvable to a line number and a log level.
Terms used throughout this module:
- Offsets are byte offsets into the log file, never character positions.
- A line ends at a line feed. A trailing carriage return is not part of its text. A line is
at most
MAX_LINE_BYTESlong (terminator included); longer lines are split at that length so that a file without line feeds cannot exhaust the memory. - A record start is a line carrying a level marker (see
RECORD_RE). The level of a line is the level of the nearest record start at or before it,CRITICALcounting asERROR. - Only complete lines are served. The unterminated end of the file is a line only once the payload is no longer running.
Classes:
| Name | Description |
|---|---|
LogChangedError |
The log file was replaced while it was read. |
LogQuery |
A compiled search query. |
LogLinesRequest |
The parameters of a request for log lines. |
IndexView |
A consistent snapshot of a line index. |
LogIndex |
Incremental line index of one log file. |
Functions:
| Name | Description |
|---|---|
get_index |
Get the line index of a log file, creating it if necessary. |
drop_index |
Discard the line index of a log file, e.g. because the file is gone. |
decode_line |
Convert the bytes of a line to its text. |
parse_levels |
Convert the level filter of a request. |
compile_query |
Compile the search query of a request. |
build_lines_request |
Check and convert the parameters of a request for log lines. |
read_log_info |
Describe a log file and the progress of its index. |
read_log_lines |
Read a window of lines from a log file. |
LogChangedError
¶
Bases: Exception
The log file was replaced while it was read.
LogQuery
dataclass
¶
LogQuery(
pattern: Pattern[str],
needle: bytes | None = None,
fold: bool = False,
extras: tuple[bytes, ...] = (),
exact: bool = False,
word_left: bool = False,
word_right: bool = False,
)
A compiled search query.
Attributes:
| Name | Type | Description |
|---|---|---|
pattern |
Pattern[str]
|
Decides whether the text of a line matches. |
needle |
bytes | None
|
Bytes every matching line contains (lower case if |
fold |
bool
|
Whether the needle has to be searched ignoring the case of ASCII letters. |
extras |
tuple[bytes, ...]
|
Byte sequences that make the needle unreliable for a piece of the log. |
exact |
bool
|
Whether the needle is the whole query: where the needle is found, the query matches, provided the word boundaries are as required. |
word_left |
bool
|
Whether an exact needle must not directly follow a word character. |
word_right |
bool
|
Whether an exact needle must not be directly followed by a word character. |
Methods:
| Name | Description |
|---|---|
matches |
Check whether a line matches the query. |
matches
¶
Check whether a line matches the query.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
bytes
|
The bytes of the line without the line feed. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if the query matches somewhere in the line. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
LogLinesRequest
dataclass
¶
LogLinesRequest(
offset: int | None = None,
line: int | None = None,
backward: bool = False,
limit: int = DEFAULT_LIMIT,
max_bytes: int = DEFAULT_MAX_BYTES,
levels: int | None = None,
query: LogQuery | None = None,
count_only: bool = False,
)
The parameters of a request for log lines.
Attributes:
| Name | Type | Description |
|---|---|---|
offset |
int | None
|
Byte offset to start at. It is snapped back to the start of its line. |
line |
int | None
|
1-based line number to start at (alternative to |
backward |
bool
|
Return the lines before the start position instead of the lines from it on. |
limit |
int
|
Maximum number of lines to return. |
max_bytes |
int
|
Soft cap for the summed size of the returned lines. |
levels |
int | None
|
Bit mask of the level codes that pass. None lets every level pass. |
query |
LogQuery | None
|
The search query. None lets every line pass. |
count_only |
bool
|
Count the passing lines instead of returning them. |
IndexView
dataclass
¶
IndexView(
ident: tuple[int, int],
size: int,
complete: bool,
indexed_bytes: int,
lines: int,
level: int,
counts: tuple[int, ...],
offsets: array,
line_nos: array,
levels: bytearray,
forced: tuple[int, ...],
checkpoints: int,
)
A consistent snapshot of a line index.
The checkpoint containers are shared with the index. They only ever grow, and a view must
not look beyond its own checkpoints count.
Attributes:
| Name | Type | Description |
|---|---|---|
ident |
tuple[int, int]
|
Device and inode of the indexed file. |
size |
int
|
Size of the file in bytes as far as known. |
complete |
bool
|
Whether the index has seen the file up to |
indexed_bytes |
int
|
End of the indexed lines. |
lines |
int
|
Number of indexed lines. |
level |
int
|
Level code in effect at |
counts |
tuple[int, ...]
|
Number of record starts per level code. |
offsets |
array
|
Byte offset of each checkpoint. Checkpoints are line starts. |
line_nos |
array
|
Number of lines before each checkpoint. |
levels |
bytearray
|
Level code in effect at each checkpoint. |
forced |
tuple[int, ...]
|
Numbers of the checkpoints that start an overlong line, which was split and therefore does not end with a line feed. |
checkpoints |
int
|
Number of checkpoints this view may use. |
LogIndex
¶
Incremental line index of one log file.
The index knows the number of lines, the number of record starts per level and a sparse list of checkpoints (byte offset, line number, level in effect). It is extended by a background thread and held in memory only; it needs about 17 bytes per checkpoint, which is some 270 KiB per gigabyte of log.
Initialize the index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
The path of the log file. |
required |
Methods:
| Name | Description |
|---|---|
refresh |
Bring the index up to date with the file and return a snapshot. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
refresh
¶
refresh(
stat_result: stat_result,
wait: float = INDEX_WAIT_SECONDS,
) -> IndexView
Bring the index up to date with the file and return a snapshot.
Starts the background thread if the file has content the index has not seen, and waits for it for a limited time. The thread keeps running after this method returned.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stat_result
|
stat_result
|
The current status of the log file. |
required |
wait
|
float
|
The maximum time in seconds to wait for the index to catch up. |
INDEX_WAIT_SECONDS
|
Returns:
| Name | Type | Description |
|---|---|---|
IndexView |
IndexView
|
The snapshot. Its |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | |
get_index
¶
get_index(path: str) -> LogIndex
Get the line index of a log file, creating it if necessary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
The path of the log file. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
LogIndex |
LogIndex
|
The index of the file. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
drop_index
¶
Discard the line index of a log file, e.g. because the file is gone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
The path of the log file. |
required |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
decode_line
¶
Convert the bytes of a line to its text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
bytes
|
The bytes of the line without the line feed. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The text of the line: without a trailing carriage return and with invalid UTF-8 replaced. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
parse_levels
¶
Convert the level filter of a request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str | None
|
Comma separated level names. None means all levels, an empty value means no level (only lines without a level pass). |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the value names an unknown level. |
Returns:
| Type | Description |
|---|---|
int | None
|
int | None: The bit mask of the passing level codes, or None if every level passes. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
compile_query
¶
compile_query(
q: str | None,
*,
regex: bool = False,
case: bool = False,
word: bool = False,
) -> LogQuery | None
Compile the search query of a request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q
|
str | None
|
The search text. None or an empty text means no query. |
required |
regex
|
bool
|
Treat the text as a regular expression (Python syntax) instead of literally. |
False
|
case
|
bool
|
Match the case exactly. |
False
|
word
|
bool
|
Match whole words only: neither end of the match may lie between two word characters. |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the text is not a valid regular expression. |
Returns:
| Type | Description |
|---|---|
LogQuery | None
|
LogQuery | None: The compiled query, or None if there is nothing to search for. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
build_lines_request
¶
build_lines_request(
*,
offset: int | None = None,
line: int | None = None,
direction: str = "forward",
limit: int = DEFAULT_LIMIT,
max_bytes: int = DEFAULT_MAX_BYTES,
levels: str | None = None,
q: str | None = None,
regex: bool = False,
case: bool = False,
word: bool = False,
count_only: bool = False,
) -> LogLinesRequest
Check and convert the parameters of a request for log lines.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
offset
|
int | None
|
The byte offset to start at. |
None
|
line
|
int | None
|
The 1-based line number to start at. Only one of |
None
|
direction
|
str
|
"forward" or "backward". |
'forward'
|
limit
|
int
|
The maximum number of lines to return (0 to |
DEFAULT_LIMIT
|
max_bytes
|
int
|
The soft cap for the summed size of the returned lines. |
DEFAULT_MAX_BYTES
|
levels
|
str | None
|
The level filter (see |
None
|
q
|
str | None
|
The search text (see |
None
|
regex
|
bool
|
Treat the search text as a regular expression. |
False
|
case
|
bool
|
Match the case of the search text. |
False
|
word
|
bool
|
Match whole words only. |
False
|
count_only
|
bool
|
Count the passing lines instead of returning them. |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If a parameter is invalid. The message says which one and why. |
Returns:
| Name | Type | Description |
|---|---|---|
LogLinesRequest |
LogLinesRequest
|
The request. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 | |
read_log_info
¶
Describe a log file and the progress of its index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
The path of the log file. |
required |
running
|
bool
|
Whether the payload writing the log is still running. |
required |
wait
|
float
|
The maximum time in seconds to wait for the index to catch up. |
INDEX_WAIT_SECONDS
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the log file does not exist. |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
The fields of the info response. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
read_log_lines
¶
read_log_lines(
path: str,
running: bool,
request: LogLinesRequest,
cancel: Event | None = None,
*,
wait: float | None = None,
budget_seconds: float | None = None,
budget_bytes: int | None = None,
) -> dict
Read a window of lines from a log file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
The path of the log file. |
required |
running
|
bool
|
Whether the payload writing the log is still running. |
required |
request
|
LogLinesRequest
|
What to read. |
required |
cancel
|
Event | None
|
Set by the caller when the result is no longer wanted; the scan then stops. |
None
|
wait
|
float | None
|
The maximum time in seconds to wait for the index to catch up. |
None
|
budget_seconds
|
float | None
|
The time after which the scan stops and reports how far it got. |
None
|
budget_bytes
|
int | None
|
The number of scanned bytes after which the scan stops. |
None
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the log file does not exist. |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
The fields of the lines response. |
Source code in packages/pyxecm/src/pyxecm_api/v1_payload/log_reader.py
1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 | |
Terminal¶
router
¶
Define a websocket to connect to a shell session in a pod.
Functions:
| Name | Description |
|---|---|
local_executable |
Find a command to run in the Customizer container. |
valid_cwd |
Check the folder a terminal is asked to start in. |
ws_terminal |
Websocket to connect to a shell session in a pod. |
local_executable
¶
Find a command to run in the Customizer container.
A path that does not exist (anymore) falls back to the command's name on the PATH: the manager asked for k9s at /root/.local/bin/k9s, where older images installed it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
command
|
str
|
command name, or path of the executable. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The executable to run. |
Source code in packages/pyxecm/src/pyxecm_api/terminal/router.py
valid_cwd
¶
Check the folder a terminal is asked to start in.
ws_terminal
async
¶
ws_terminal(
websocket: WebSocket,
pod: str = Query(...),
command: str = Query(...),
container: str = Query(None),
cwd: str = Query(None),
) -> None
Websocket to connect to a shell session in a pod.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
websocket
|
WebSocket
|
WebSocket to connect to the shell session. |
required |
pod
|
str
|
pod name to connect to. |
Query(...)
|
command
|
str
|
command to be executed. |
Query(...)
|
container
|
str
|
container name to connect to. |
Query(None)
|
cwd
|
str
|
folder to start the command in (absolute). |
Query(None)
|
Source code in packages/pyxecm/src/pyxecm_api/terminal/router.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |