Skip to content

Commit

Permalink
fix: drop mongodb for sqlite to limit the amount of needed containers
Browse files Browse the repository at this point in the history
  • Loading branch information
jaypyles committed Dec 18, 2024
1 parent 37f70ef commit af9b7aa
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 164 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,5 @@ cython_debug/

.next

configs/*
configs/*
data
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,14 @@ commands:
Integrations must be placed underneath `/dashboard/configs/integrations` with the name `<name>.yml`

The `display_url` is optional and is used if you want to display a different url than the `url` in the integration, for example: http://192.168.1.100:8096 is the jellyfin url, but you want to display the jellyfin dashboard url located https://yourdomain.com/jellyfin.

Example integration:

```yaml
url: <url>
api_key: <api_key>
display_url: <display_url> # optional
```

### Editing the Background
Expand Down
3 changes: 3 additions & 0 deletions api/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
from api.backend.routers.config import config_router
from api.backend.routers.command import command_router
from api.backend.routers.integration import integration_router
from api.backend.database import create_database


create_database()

app = FastAPI(title="api")

app.add_middleware(
Expand Down
3 changes: 3 additions & 0 deletions api/backend/database/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .startup import create_database

__all__ = ["create_database"]
170 changes: 86 additions & 84 deletions api/backend/database/database_functions.py
Original file line number Diff line number Diff line change
@@ -1,135 +1,134 @@
# STL
import uuid
import json
from typing import Any, Union, Literal, Optional
import sqlite3

# PDM
import motor.motor_asyncio
from bson import CodecOptions, InvalidDocument
from fastapi.responses import JSONResponse
from typing_extensions import override
from bson.codec_options import TypeEncoder, TypeRegistry

# LOCAL
from api.backend.models import Job
from api.backend.configs.schema import Arg, Command
from api.backend.database.startup import DATABASE_PATH


class CommandEncoder(TypeEncoder):
@override
def transform_python(self, value: Any):
if isinstance(value, Command):
return {"name": value.name, "command": value.name, "args": value.args}

raise InvalidDocument(f"Cannot encode object: {value}, of type: {type(value)}")

@property
@override
def python_type(self):
return Command


class ArgEncoder(TypeEncoder):
@override
def transform_python(self, value: Any):
if isinstance(value, Arg):
return {"flag": value.flag, "value": value.value}

raise InvalidDocument(f"Cannot encode object: {value}, of type: {type(value)}")

@property
@override
def python_type(self):
return Arg


command_encoder = CommandEncoder()
arg_encoder = ArgEncoder()
codec_options: CodecOptions[dict[str, Any]] = CodecOptions(
type_registry=TypeRegistry([command_encoder, arg_encoder])
)


async def get_client():
client: motor.motor_asyncio.AsyncIOMotorClient[dict[str, Any]] = (
motor.motor_asyncio.AsyncIOMotorClient(
"mongodb://root:example@manager-mongo:27017"
)
)
return client


async def get_job_collection():
client = await get_client()
db = client["manager"].with_options(codec_options=codec_options)
collection = db["jobs"]
return collection
async def get_connection():
conn = sqlite3.connect(DATABASE_PATH)
conn.row_factory = sqlite3.Row
return conn


async def insert_job(job: Job):
collection = await get_job_collection()
connection = await get_connection()
dict_job = dict(job)
dict_job.update(
{
"id": uuid.uuid4().hex,
"commands": [dict(command) for command in dict_job["commands"]],
}
)
_ = collection.insert_one(dict_job)
cursor = connection.cursor()
_ = cursor.execute(
"INSERT INTO jobs (id, host, status, time_created, commands, output) VALUES (?, ?, ?, ?, ?, ?)",
(
dict_job["id"],
dict_job["host"],
dict_job["status"],
dict_job["time_created"],
json.dumps(dict_job["commands"]),
json.dumps(dict_job["output"]),
),
)
connection.commit()
connection.close()


async def get_queued_job():
collection = await get_job_collection()
return await collection.find_one({"status": "queued"}, sort=[("time_created", 1)])
connection = await get_connection()
cursor = connection.cursor()
_ = cursor.execute(
"SELECT * FROM jobs WHERE status = 'queued' ORDER BY time_created ASC LIMIT 1"
)

job = cursor.fetchone()

connection.commit()
connection.close()

if job is None:
return None

return {
"id": job[0],
"host": job[1],
"status": job[2],
"time_created": job[3],
"commands": json.loads(job[4]),
"output": json.loads(job[5]),
}


async def update_job_status(
id: str,
status: Union[Literal["queued"], Literal["done"], Literal["running"]],
):
collection = await get_job_collection()

_ = await collection.update_one({"id": id}, {"$set": {"status": status}})
connection = await get_connection()
cursor = connection.cursor()
_ = cursor.execute("UPDATE jobs SET status = ? WHERE id = ?", (status, id))
connection.commit()
connection.close()


async def update_job(
id: str,
command_name: str,
output: Optional[dict[str, Any]] = None,
):
collection = await get_job_collection()
connection = await get_connection()
cursor = connection.cursor()
new_output = {}

_ = await collection.update_one(
{"id": id, "output": None}, {"$set": {"output": {}}}
commands = json.loads(
cursor.execute("SELECT commands FROM jobs WHERE id = ?", (id,)).fetchone()[0]
)
result = await collection.update_one(
{"id": id}, {"$set": {f"output.{command_name}": output}}

if output is None:
output = {"stdout": "", "stderr": ""}

for command in commands:
if command["name"] == command_name:
new_output[command_name] = output

_ = cursor.execute(
f"UPDATE jobs SET output = ? WHERE id = ?",
(json.dumps(new_output), id),
)

if result.modified_count == 0:
connection.commit()
connection.close()

if cursor.rowcount == 0:
raise Exception(f"Job with id {id} was not updated.")

return {"status": "success"}


async def retrieve_jobs(host: str):
collection = await get_job_collection()
jobs: list[Job] = []

async for job in collection.find({"host": host}):
job_model = Job(
id=job["id"],
host=job["host"],
status=job["status"],
time_created=job["time_created"],
commands=job["commands"],
output=job["output"],
)
jobs.append(job_model)
connection = await get_connection()
cursor = connection.cursor()
_ = cursor.execute("SELECT * FROM jobs WHERE host = ?", (host,))
jobs = cursor.fetchall()

connection.close()

jobs_as_dicts = [
{
**job.model_dump(),
"time_created": job.time_created.strftime("%B %d, %H:%M:%S %Y"),
"id": job[0],
"host": job[1],
"status": job[2],
"time_created": job[3],
"commands": json.loads(job[4]),
"output": json.loads(job[5]),
}
for job in jobs
]
Expand All @@ -138,6 +137,9 @@ async def retrieve_jobs(host: str):


async def delete_job(id: str):
collection = await get_job_collection()
connection = await get_connection()
cursor = connection.cursor()
print(f"Deleting job with id: {id}")
_ = await collection.delete_many({"id": id})
_ = cursor.execute("DELETE FROM jobs WHERE id = ?", (id,))
connection.commit()
connection.close()
29 changes: 29 additions & 0 deletions api/backend/database/startup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import sqlite3
import os

DATA_FOLDER = "./data"
DATABASE_PATH = f"{DATA_FOLDER}/jobs.db"


def create_database():
os.makedirs(DATA_FOLDER, exist_ok=True)
conn = sqlite3.connect(DATABASE_PATH)
create_tables(conn)
conn.close()


def create_tables(conn: sqlite3.Connection):
cursor = conn.cursor()
_ = cursor.execute(
"""
CREATE TABLE IF NOT EXISTS jobs (
id TEXT PRIMARY KEY,
host TEXT,
status TEXT,
time_created DATETIME DEFAULT CURRENT_TIMESTAMP,
commands JSON,
output JSON
)
"""
)
conn.commit()
22 changes: 17 additions & 5 deletions api/backend/routers/integration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# STL
import logging
from typing import Optional, TypedDict

# PDM
from fastapi import APIRouter
Expand All @@ -20,12 +21,23 @@
integration_router = APIRouter()


class Integration(TypedDict):
name: str
url: str


@integration_router.get("/api/integrations")
async def get_integrations():
return [
{"name": integration.name, "url": integration.config["url"]}
for integration in INTEGRATIONS
]
async def get_integrations() -> list[Integration]:
integrations = []

for integration in INTEGRATIONS:
url = integration.config["url"]
if integration.config.get("display_url"):
url = integration.config["display_url"]

integrations.append(Integration(name=integration.name, url=url))

return integrations


@integration_router.get("/api/integrations/uptime")
Expand Down
3 changes: 3 additions & 0 deletions charts/dashboard/templates/dashboard-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ spec:
- mountPath: /project/configs
subPath: {{ .Values.app.metadata.name }}/configs
name: config-volume
- mountPath: /project/data
subPath: {{ .Values.app.metadata.name }}/data
name: data-volume
ports:
- containerPort: 3000
env:
Expand Down
12 changes: 0 additions & 12 deletions charts/dashboard/templates/dashboard-doppler.yaml

This file was deleted.

38 changes: 0 additions & 38 deletions charts/dashboard/templates/dashboard-mongo.yaml

This file was deleted.

Loading

0 comments on commit af9b7aa

Please sign in to comment.