Skip to content

Commit

Permalink
Add a new feature to monitor all things docker
Browse files Browse the repository at this point in the history
Update sample values
  • Loading branch information
dormant-user committed Aug 11, 2024
1 parent 3c9cdce commit 5887b7e
Show file tree
Hide file tree
Showing 14 changed files with 445 additions and 31 deletions.
5 changes: 5 additions & 0 deletions doc_gen/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ Routers
Monitors
========

Docker
------

.. automodule:: pyninja.dockerized

Process
-------

Expand Down
5 changes: 5 additions & 0 deletions docs/_sources/index.rst.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ Routers
Monitors
========

Docker
------

.. automodule:: pyninja.dockerized

Process
-------

Expand Down
33 changes: 29 additions & 4 deletions docs/genindex.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,16 @@ <h2 id="D">D</h2>
<li><a href="index.html#pyninja.models.Database">Database (class in pyninja.models)</a>
</li>
<li><a href="index.html#pyninja.models.EnvConfig.database">database (pyninja.models.EnvConfig attribute)</a>
</li>
<li><a href="index.html#pyninja.models.ServiceStatus.description">description (pyninja.models.ServiceStatus attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="index.html#pyninja.models.ServiceStatus.description">description (pyninja.models.ServiceStatus attribute)</a>
<li><a href="index.html#pyninja.routers.docker_containers">docker_containers() (in module pyninja.routers)</a>
</li>
<li><a href="index.html#pyninja.routers.docker_images">docker_images() (in module pyninja.routers)</a>
</li>
<li><a href="index.html#pyninja.routers.docker_volumes">docker_volumes() (in module pyninja.routers)</a>
</li>
<li><a href="index.html#pyninja.routers.docs">docs() (in module pyninja.routers)</a>
</li>
Expand Down Expand Up @@ -144,15 +150,25 @@ <h2 id="F">F</h2>
<h2 id="G">G</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="index.html#pyninja.dockerized.get_all_containers">get_all_containers() (in module pyninja.dockerized)</a>
</li>
<li><a href="index.html#pyninja.dockerized.get_all_images">get_all_images() (in module pyninja.dockerized)</a>
</li>
<li><a href="index.html#pyninja.routers.get_all_routes">get_all_routes() (in module pyninja.routers)</a>
</li>
<li><a href="index.html#pyninja.process.get_performance">get_performance() (in module pyninja.process)</a>
<li><a href="index.html#pyninja.dockerized.get_all_volumes">get_all_volumes() (in module pyninja.dockerized)</a>
</li>
<li><a href="index.html#pyninja.dockerized.get_container_status">get_container_status() (in module pyninja.dockerized)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="index.html#pyninja.process.get_performance">get_performance() (in module pyninja.process)</a>
</li>
<li><a href="index.html#pyninja.process.get_process_status">get_process_status() (in module pyninja.process)</a>
</li>
<li><a href="index.html#pyninja.database.get_record">get_record() (in module pyninja.database)</a>
</li>
<li><a href="index.html#pyninja.dockerized.get_running_containers">get_running_containers() (in module pyninja.dockerized)</a>
</li>
<li><a href="index.html#pyninja.service.get_service_status">get_service_status() (in module pyninja.service)</a>
</li>
Expand Down Expand Up @@ -209,6 +225,8 @@ <h2 id="M">M</h2>
<li><a href="index.html#module-pyninja.auth">pyninja.auth</a>
</li>
<li><a href="index.html#module-pyninja.database">pyninja.database</a>
</li>
<li><a href="index.html#module-pyninja.dockerized">pyninja.dockerized</a>
</li>
<li><a href="index.html#module-pyninja.exceptions">pyninja.exceptions</a>
</li>
Expand Down Expand Up @@ -267,6 +285,13 @@ <h2 id="P">P</h2>

<ul>
<li><a href="index.html#module-pyninja.database">module</a>
</li>
</ul></li>
<li>
pyninja.dockerized

<ul>
<li><a href="index.html#module-pyninja.dockerized">module</a>
</li>
</ul></li>
<li>
Expand All @@ -276,15 +301,15 @@ <h2 id="P">P</h2>
<li><a href="index.html#module-pyninja.exceptions">module</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
pyninja.main

<ul>
<li><a href="index.html#module-pyninja.main">module</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
pyninja.models

Expand Down
143 changes: 134 additions & 9 deletions docs/index.html

Large diffs are not rendered by default.

Binary file modified docs/objects.inv
Binary file not shown.
5 changes: 5 additions & 0 deletions docs/py-modindex.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ <h1>Python Module Index</h1>
<td>&#160;&#160;&#160;
<a href="index.html#module-pyninja.database"><code class="xref">pyninja.database</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
<a href="index.html#module-pyninja.dockerized"><code class="xref">pyninja.dockerized</code></a></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&#160;&#160;&#160;
Expand Down
2 changes: 1 addition & 1 deletion docs/searchindex.js

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions pyninja/dockerized.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import logging
from collections.abc import Generator
from typing import Dict, List

import docker
from docker.errors import DockerException

LOGGER = logging.getLogger("uvicorn.default")


def get_container_status(name: str = None) -> str | None:
"""Get container status by name.
Args:
name: Name of the container or image used to run the container.
Returns:
str:
Container status as a string.
"""
try:
containers = docker.from_env().api.containers()
except DockerException as error:
LOGGER.error(error)
return
for container in containers:
if name in container.get("Image") or name in container.get("Names"):
return (
f"{container.get('Id')[:12]} - {container.get('Names')} - "
f"{container.get('State')} - {container.get('Status')}"
)


def get_running_containers() -> Generator[Dict[str, str]]:
"""Get running containers.
Yields:
Dict[str, str]:
Yields a dictionary of running containers with the corresponding metrics.
"""
try:
containers = docker.from_env().api.containers()
except DockerException as error:
LOGGER.error(error)
return []
for container in containers:
if container.get("State") == "running":
yield container


def get_all_containers() -> List[Dict[str, str]] | None:
"""Get all containers and their metrics.
Returns:
List[Dict[str, str]]:
Returns a list of all the containers and their stats.
"""
try:
return docker.from_env().api.containers(all=True)
except DockerException as error:
LOGGER.error(error)
return


def get_all_images() -> Dict[str, str] | None:
"""Get all docker images.
Returns:
Dict[str, str]:
Returns a dictionary with image stats.
"""
try:
return docker.from_env().api.images(all=True)
except DockerException as error:
LOGGER.error(error)
return


def get_all_volumes() -> Dict[str, str] | None:
"""Get all docker volumes.
Returns:
Dict[str, str]:
Returns a dictionary with list of volume objects.
"""
try:
return docker.from_env().api.volumes()
except DockerException as error:
LOGGER.error(error)
return
18 changes: 9 additions & 9 deletions pyninja/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ def start(**kwargs) -> None:
"""Starter function for the API, which uses uvicorn server as trigger.
Keyword Args:
- env_file - Env filepath to load the environment variables.
- ninja_host - Hostname for the API server.
- ninja_port - Port number for the API server.
- workers - Number of workers for the uvicorn server.
- remote_execution - Boolean flag to enable remote execution.
- api_secret - Secret access key for running commands on server remotely.
- database - FilePath to store the auth database that handles the authentication errors.
- rate_limit - List of dictionaries with `max_requests` and `seconds` to apply as rate limit.
- apikey - API Key for authentication.
env_file: Env filepath to load the environment variables.
ninja_host: Hostname for the API server.
ninja_port: Port number for the API server.
workers: Number of workers for the uvicorn server.
remote_execution: Boolean flag to enable remote execution.
api_secret: Secret access key for running commands on server remotely.
database: FilePath to store the auth database that handles the authentication errors.
rate_limit: List of dictionaries with ``max_requests`` and ``seconds`` to apply as rate limit.
apikey: API Key for authentication.
"""
if env_file := kwargs.get("env_file"):
models.env = squire.env_loader(env_file)
Expand Down
135 changes: 134 additions & 1 deletion pyninja/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from pydantic import PositiveFloat, PositiveInt

from pyninja import auth, exceptions, models, process, rate_limit, service, squire
from pyninja import (
auth,
dockerized,
exceptions,
models,
process,
rate_limit,
service,
squire,
)

LOGGER = logging.getLogger("uvicorn.default")
security = HTTPBearer()
Expand Down Expand Up @@ -109,6 +118,112 @@ async def service_status(
)


async def docker_containers(
request: Request,
container_name: str = None,
get_all: bool = False,
get_running: bool = False,
apikey: HTTPAuthorizationCredentials = Depends(security),
):
"""**API function to get docker containers' information.**
**Args:**
request: Reference to the FastAPI request object.
container_name: Name of the container to check status.
get_all: Get all the containers' information.
get_running: Get running containers' information.
apikey: API Key to authenticate the request.
**Raises:**
APIResponse:
Raises the HTTPStatus object with a status code and detail as response.
"""
await auth.level_1(request, apikey)
if get_all:
if all_containers := dockerized.get_all_containers():
raise exceptions.APIResponse(
status_code=HTTPStatus.OK.real, detail=all_containers
)
raise exceptions.APIResponse(
status_code=HTTPStatus.NOT_FOUND.real, detail="No containers found!"
)
if get_running:
if running_containers := list(dockerized.get_running_containers()):
raise exceptions.APIResponse(
status_code=HTTPStatus.OK.real, detail=running_containers
)
raise exceptions.APIResponse(
status_code=HTTPStatus.NOT_FOUND.real, detail="No running containers found!"
)
if container_name:
if container_status := dockerized.get_container_status(container_name):
raise exceptions.APIResponse(
status_code=HTTPStatus.OK.real, detail=container_status
)
raise exceptions.APIResponse(
status_code=HTTPStatus.SERVICE_UNAVAILABLE.real,
detail="Unable to get container status!",
)
raise exceptions.APIResponse(
status_code=HTTPStatus.BAD_REQUEST.real,
detail="Either 'container_name' or 'get_all' or 'get_running' should be set",
)


async def docker_images(
request: Request,
apikey: HTTPAuthorizationCredentials = Depends(security),
):
"""**API function to get docker images' information.**
**Args:**
request: Reference to the FastAPI request object.
apikey: API Key to authenticate the request.
**Raises:**
APIResponse:
Raises the HTTPStatus object with a status code and detail as response.
"""
await auth.level_1(request, apikey)
if images := dockerized.get_all_images():
LOGGER.info(images)
raise exceptions.APIResponse(status_code=HTTPStatus.OK.real, detail=images)
raise exceptions.APIResponse(
status_code=HTTPStatus.SERVICE_UNAVAILABLE.real,
detail="Unable to get docker images!",
)


async def docker_volumes(
request: Request,
apikey: HTTPAuthorizationCredentials = Depends(security),
):
"""**API function to get docker volumes' information.**
**Args:**
request: Reference to the FastAPI request object.
apikey: API Key to authenticate the request.
**Raises:**
APIResponse:
Raises the HTTPStatus object with a status code and detail as response.
"""
await auth.level_1(request, apikey)
if volumes := dockerized.get_all_volumes():
LOGGER.info(volumes)
raise exceptions.APIResponse(status_code=HTTPStatus.OK.real, detail=volumes)
raise exceptions.APIResponse(
status_code=HTTPStatus.SERVICE_UNAVAILABLE.real,
detail="Unable to get docker volumes!",
)


async def docs() -> RedirectResponse:
"""Redirect to docs page.
Expand Down Expand Up @@ -144,6 +259,24 @@ def get_all_routes() -> List[APIRoute]:
methods=["GET"],
dependencies=dependencies,
),
APIRoute(
path="/docker-container",
endpoint=docker_containers,
methods=["GET"],
dependencies=dependencies,
),
APIRoute(
path="/docker-image",
endpoint=docker_images,
methods=["GET"],
dependencies=dependencies,
),
APIRoute(
path="/docker-volume",
endpoint=docker_volumes,
methods=["GET"],
dependencies=dependencies,
),
]
if all((models.env.remote_execution, models.env.api_secret)):
routes.append(
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
click==8.1.7
docker==7.1.0
fastapi==0.112.0
psutil==6.0.0
pydantic==2.8.2
Expand Down
Loading

0 comments on commit 5887b7e

Please sign in to comment.