Skip to content

Commit

Permalink
Merge pull request #71 from UpstreamDataInc/dev_settings
Browse files Browse the repository at this point in the history
  • Loading branch information
b-rowan authored Aug 16, 2024
2 parents c787cb4 + be3f048 commit 1c58387
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 92 deletions.
4 changes: 2 additions & 2 deletions goosebit/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from tortoise import Tortoise, run_async

from goosebit.models import Firmware
from goosebit.settings import DB_MIGRATIONS_LOC, DB_URI
from goosebit.settings import DB_MIGRATIONS_LOC, config

TORTOISE_CONF = {
"connections": {"default": DB_URI},
"connections": {"default": config.db_uri},
"apps": {
"models": {
"models": ["goosebit.models", "aerich.models"],
Expand Down
14 changes: 8 additions & 6 deletions goosebit/permissions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from enum import Enum
from typing import TypeVar, cast
from typing import ClassVar, TypeVar, cast

from pydantic import BaseModel

T = TypeVar("T", bound="PermissionsBase")

Expand Down Expand Up @@ -38,11 +40,11 @@ class HomePermissions(PermissionsBase):
READ = "home.read"


class Permissions:
HOME = HomePermissions
FIRMWARE = FirmwarePermissions
DEVICE = DevicePermissions
ROLLOUT = RolloutPermissions
class Permissions(BaseModel):
HOME: ClassVar = HomePermissions
FIRMWARE: ClassVar = FirmwarePermissions
DEVICE: ClassVar = DevicePermissions
ROLLOUT: ClassVar = RolloutPermissions

@classmethod
def full(cls) -> set[T]:
Expand Down
62 changes: 0 additions & 62 deletions goosebit/settings.py

This file was deleted.

6 changes: 6 additions & 0 deletions goosebit/settings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .const import BASE_DIR, DB_MIGRATIONS_LOC, PWD_CXT, SECRET # noqa: F401
from .schema import GooseBitSettings

config = GooseBitSettings()

USERS = {u.username: u for u in config.users}
23 changes: 23 additions & 0 deletions goosebit/settings/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import secrets
from pathlib import Path

from argon2 import PasswordHasher
from joserfc.jwk import OctKey

BASE_DIR = Path(__file__).resolve().parent.parent
DB_MIGRATIONS_LOC = BASE_DIR.joinpath("migrations")

SECRET = OctKey.import_key(secrets.token_hex(16))
PWD_CXT = PasswordHasher()

LOGGING_DEFAULT = {
"version": 1,
"formatters": {"simple": {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"}},
"handlers": {"console": {"class": "logging.StreamHandler", "formatter": "simple", "level": "DEBUG"}},
"loggers": {
"tortoise": {"handlers": ["console"], "level": "WARNING", "propagate": True},
"aiosqlite": {"handlers": ["console"], "level": "WARNING", "propagate": True},
"multipart": {"handlers": ["console"], "level": "INFO", "propagate": True},
},
"root": {"level": "INFO", "handlers": ["console"]},
}
75 changes: 75 additions & 0 deletions goosebit/settings/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from pathlib import Path
from typing import Annotated, Iterable

from pydantic import BaseModel, BeforeValidator, Field
from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
SettingsConfigDict,
YamlConfigSettingsSource,
)

from goosebit.permissions import Permissions, PermissionsBase

from .const import BASE_DIR, LOGGING_DEFAULT, PWD_CXT


def parse_permissions(items: Iterable[str]):
permissions = set()
for p in items:
permissions.update(Permissions.from_str(p))
return permissions


class User(BaseModel):
username: str
hashed_pwd: Annotated[str, BeforeValidator(PWD_CXT.hash)] = Field(validation_alias="password")
permissions: Annotated[set[PermissionsBase], BeforeValidator(parse_permissions)]

def get_json_permissions(self):
return [str(p) for p in self.permissions]


class PrometheusSettings(BaseModel):
enable: bool = False
port: int = 9090


class MetricsSettings(BaseModel):
prometheus: PrometheusSettings = PrometheusSettings()


class GooseBitSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="GOOSEBIT_")

artifacts_dir: Path = BASE_DIR.joinpath("artifacts")

tenant: str = "DEFAULT"

poll_time_default: str = "00:01:00"
poll_time_updating: str = "00:00:05"
poll_time_registration: str = "00:00:10"

users: list[User] = []

db_uri: str = f"sqlite:///{BASE_DIR.joinpath('db.sqlite3')}"

metrics: MetricsSettings = MetricsSettings()

logging: dict = LOGGING_DEFAULT

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
return (
init_settings,
YamlConfigSettingsSource(settings_cls, BASE_DIR.parent.joinpath("settings.yaml")),
env_settings,
file_secret_settings,
)
7 changes: 3 additions & 4 deletions goosebit/telemetry/prometheus.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from prometheus_client import start_http_server

from goosebit import settings

PROMETHEUS_PORT = settings.config.get("metrics", {}).get("prometheus", {}).get("port", 9090)
from goosebit.settings import config

# separate file to enable it as a feature later.
reader = PrometheusMetricReader()
start_http_server(port=PROMETHEUS_PORT, addr="0.0.0.0")
if config.metrics.prometheus.enable:
start_http_server(port=config.metrics.prometheus.port, addr="0.0.0.0")
4 changes: 2 additions & 2 deletions goosebit/ui/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from goosebit.auth import authenticate_session, validate_user_permissions
from goosebit.models import Firmware, Rollout
from goosebit.permissions import Permissions
from goosebit.settings import ARTIFACTS_DIR
from goosebit.settings import config
from goosebit.ui.nav import nav
from goosebit.ui.templates import templates
from goosebit.updates import create_firmware_update
Expand Down Expand Up @@ -51,7 +51,7 @@ async def upload_update_local(
done: bool = Form(...),
filename: str = Form(...),
):
file = ARTIFACTS_DIR.joinpath(filename)
file = config.artifacts_dir.joinpath(filename)

temp_file = file.with_suffix(".tmp")
if init:
Expand Down
4 changes: 2 additions & 2 deletions goosebit/updater/controller/v1/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fastapi.requests import Request

from goosebit.models import Firmware, UpdateStateEnum
from goosebit.settings import POLL_TIME_REGISTRATION
from goosebit.settings import config
from goosebit.updater.manager import HandlingType, UpdateManager, get_update_manager
from goosebit.updates import generate_chunk

Expand All @@ -28,7 +28,7 @@ async def polling(

if device.last_state == UpdateStateEnum.UNKNOWN:
# device registration
sleep = POLL_TIME_REGISTRATION
sleep = config.poll_time_registration
links["configData"] = {
"href": str(
request.url_for(
Expand Down
16 changes: 8 additions & 8 deletions goosebit/updater/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
UpdateModeEnum,
UpdateStateEnum,
)
from goosebit.settings import POLL_TIME, POLL_TIME_UPDATING
from goosebit.settings import config

caches.set_config(
{
Expand Down Expand Up @@ -111,11 +111,11 @@ def log_subscribers(self, value: list):

@property
def poll_time(self):
return UpdateManager.device_poll_time.get(self.dev_id, POLL_TIME)
return UpdateManager.device_poll_time.get(self.dev_id, config.poll_time_default)

@poll_time.setter
def poll_time(self, value: str):
if not value == POLL_TIME:
if not value == config.poll_time_default:
UpdateManager.device_poll_time[self.dev_id] = value
return
if self.dev_id in UpdateManager.device_poll_time:
Expand All @@ -135,7 +135,7 @@ async def update_log(self, log_data: str) -> None: ...
class UnknownUpdateManager(UpdateManager):
def __init__(self, dev_id: str):
super().__init__(dev_id)
self.poll_time = POLL_TIME_UPDATING
self.poll_time = config.poll_time_updating

async def _get_firmware(self) -> Firmware:
return await Firmware.latest(await self.get_device())
Expand Down Expand Up @@ -282,19 +282,19 @@ async def get_update(self) -> tuple[HandlingType, Firmware]:

if firmware is None:
handling_type = HandlingType.SKIP
self.poll_time = POLL_TIME
self.poll_time = config.poll_time_default

elif firmware.version == device.fw_version and not device.force_update:
handling_type = HandlingType.SKIP
self.poll_time = POLL_TIME
self.poll_time = config.poll_time_default

elif device.last_state == UpdateStateEnum.ERROR and not device.force_update:
handling_type = HandlingType.SKIP
self.poll_time = POLL_TIME
self.poll_time = config.poll_time_default

else:
handling_type = HandlingType.FORCED
self.poll_time = POLL_TIME_UPDATING
self.poll_time = config.poll_time_updating

if device.log_complete:
await self.update_log_complete(False)
Expand Down
4 changes: 2 additions & 2 deletions goosebit/updater/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.requests import Request

from goosebit.settings import TENANT
from goosebit.settings import config

from . import controller
from .manager import get_update_manager


async def verify_tenant(tenant: str):
if not tenant == TENANT:
if not tenant == config.tenant:
raise HTTPException(404)
return tenant

Expand Down
5 changes: 3 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import uvicorn

from goosebit import app, settings
from goosebit import app
from goosebit.settings import config

logging.config.dictConfig(settings.LOGGING)
logging.config.dictConfig(config.logging)

uvicorn_args = {"port": 80}

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ opentelemetry-instrumentation-fastapi = "^0.47b0"
opentelemetry-exporter-prometheus = "^0.47b0"
aiocache = "^0.12.2"
httpx = "^0.27.0"
pydantic-settings = "^2.4.0"

asyncpg = { version = "^0.29.0", optional = true }

Expand Down
6 changes: 4 additions & 2 deletions settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ poll_time_updating: 00:00:05
# "device.read", "device.write", "device.delete"
# "rollout.read", "rollout.write", "rollout.delete"
# "home.read"

users:
- email: [email protected]
- username: [email protected]
password: admin
permissions:
- "*"
- email: [email protected]
- username: [email protected]
password: ops
permissions:
- "home.read"
Expand All @@ -33,6 +34,7 @@ poll_time_registration: 00:00:10
tenant: DEFAULT
metrics:
prometheus:
enable: false
port: 9090
logging:
version: 1
Expand Down

0 comments on commit 1c58387

Please sign in to comment.