Merge pull request 'CI: docker image & woodpecker' (#13) from dockerify into develop
Reviewed-on: https://hattori.ztsh.eu/iac/karl/pulls/13
This commit is contained in:
commit
846f22b8e1
40 changed files with 220 additions and 41 deletions
62
.dockerignore
Normal file
62
.dockerignore
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# JB Toolkit
|
||||
.idea
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# CI
|
||||
.woodpecker/
|
||||
|
||||
# Virtual env
|
||||
.venv/
|
||||
|
||||
# Docker
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
.docker
|
||||
.dockerignore
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
**/__pycache__/
|
||||
**/*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
target/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Logs
|
||||
**/*.log
|
||||
|
||||
# Documentation
|
||||
docs/_build/
|
||||
|
||||
# Python mode for VIM
|
||||
.ropeproject
|
||||
**/.ropeproject
|
||||
|
||||
# Vim swap files
|
||||
**/*.swp
|
||||
|
||||
# Project specific
|
||||
.compose_repository/
|
||||
config/
|
||||
tests/
|
||||
|
|
@ -8,3 +8,6 @@ trim_trailing_whitespace = true
|
|||
|
||||
[*.py]
|
||||
indent_size = 2
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
||||
|
|
|
|||
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -1,3 +1,13 @@
|
|||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
.idea
|
||||
*.iml
|
||||
uv.lock
|
||||
|
|
|
|||
1
.python-version
Normal file
1
.python-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
3.12
|
||||
14
.woodpecker/dev.yaml
Normal file
14
.woodpecker/dev.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
steps:
|
||||
- name: build
|
||||
image: woodpeckerci/plugin-docker-buildx:5.2.2
|
||||
settings:
|
||||
platforms: linux/amd64
|
||||
repo: hattori.ztsh.eu/iac/karl
|
||||
registry: hattori.ztsh.eu
|
||||
tags: dev-${CI_PIPELINE_NUMBER}
|
||||
username: stawros
|
||||
password:
|
||||
from_secret: hattori-packages
|
||||
when:
|
||||
- event: pull_request
|
||||
evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "ci-ready"'
|
||||
14
.woodpecker/latest.yaml
Normal file
14
.woodpecker/latest.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
steps:
|
||||
- name: build
|
||||
image: woodpeckerci/plugin-docker-buildx:5.2.2
|
||||
settings:
|
||||
platforms: linux/amd64
|
||||
repo: hattori.ztsh.eu/iac/karl
|
||||
registry: hattori.ztsh.eu
|
||||
tags: latest
|
||||
username: stawros
|
||||
password:
|
||||
from_secret: hattori-packages
|
||||
when:
|
||||
- event: [ tag, push, manual ]
|
||||
branch: master
|
||||
33
Dockerfile
Normal file
33
Dockerfile
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
FROM ghcr.io/astral-sh/uv:0.9-python3.12-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk update \
|
||||
&& apk add gcc python3-dev musl-dev linux-headers
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
uv sync --no-install-workspace
|
||||
|
||||
ADD . /app
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --locked
|
||||
|
||||
FROM python:3.12-alpine3.22
|
||||
|
||||
RUN apk update --no-cache \
|
||||
&& apk add --no-cache git
|
||||
|
||||
COPY --from=builder --chown=app:app /app/.venv /app/.venv
|
||||
COPY --from=builder --chown=app:app /app/src /app/src
|
||||
|
||||
ENV PYTHONPATH="/app"
|
||||
|
||||
EXPOSE 8081
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENTRYPOINT ["/app/.venv/bin/python"]
|
||||
|
||||
CMD ["/app/src/karl/__init__.py"]
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
logging:
|
||||
level: "TRACE"
|
||||
path: "logs/karl.log"
|
||||
path: "../../logs/karl.log"
|
||||
app:
|
||||
host: "127.0.0.1"
|
||||
port: 8081
|
||||
|
|
|
|||
22
docker-compose.yaml
Normal file
22
docker-compose.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
services:
|
||||
karl:
|
||||
image: hattori.ztsh.eu/iac/karl:dev-36
|
||||
container_name: karl
|
||||
restart: always
|
||||
networks:
|
||||
- web
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./data/config:/app/config
|
||||
- ./data/logs:/app/logs
|
||||
secrets:
|
||||
- kp_secret
|
||||
|
||||
secrets:
|
||||
kp_secret:
|
||||
file: ./data/kp_secret
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
|
|
@ -5,6 +5,7 @@ description = "Because name 'Jenkins' was already taken. Greatest composer ever.
|
|||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
authors = [{ name = "Piotr Dec" }]
|
||||
|
||||
dependencies = [
|
||||
"fastapi>=0.119.0",
|
||||
"uvicorn[standard]>=0.30.0",
|
||||
|
|
@ -18,6 +19,7 @@ dependencies = [
|
|||
"py-automapper>=2.2.0",
|
||||
"fastapi-utils>=0.8.0",
|
||||
"keyring>=25.6.0",
|
||||
"keyring-backend>=0.1.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
|
|
@ -31,11 +33,11 @@ dev = [
|
|||
]
|
||||
|
||||
[project.scripts]
|
||||
app = "app.main:run"
|
||||
karl = "karl.main:run"
|
||||
|
||||
[tool.uv]
|
||||
# uv automatycznie wykrywa dependencies z [project]
|
||||
# Możesz dodać tu własne ustawienia cache/mirrors, jeśli potrzebne.
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.8.23,<0.9.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
|
|
|
|||
19
src/karl/__init__.py
Normal file
19
src/karl/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from config import get_settings
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import uvicorn
|
||||
|
||||
settings = get_settings()
|
||||
uvicorn.run(
|
||||
"karl.main:run",
|
||||
factory=True,
|
||||
host=settings.app.host,
|
||||
port=settings.app.port,
|
||||
reload=settings.app.reload,
|
||||
log_config=None,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -3,10 +3,10 @@ from fastapi import APIRouter, Depends
|
|||
from fastapi_utils.cbv import cbv
|
||||
from starlette.responses import JSONResponse, Response
|
||||
|
||||
from app.api.models import Request
|
||||
from app.core.injects import AutowireSupport
|
||||
from app.core.woodpecker import Woodpecker
|
||||
from app.model.webhook import WoodpeckerEvent
|
||||
from api.models import Request
|
||||
from core.injects import AutowireSupport
|
||||
from core.woodpecker import Woodpecker
|
||||
from model.webhook import WoodpeckerEvent
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
@ -8,13 +8,13 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||
|
||||
class LoggingConfig(BaseModel):
|
||||
level: str = "INFO"
|
||||
path: Path = Path("logs/karl.log")
|
||||
path: Path | None = None
|
||||
|
||||
|
||||
class AppConfig(BaseModel):
|
||||
host: str = "127.0.0.1"
|
||||
port: int = 8081
|
||||
reload: bool = True
|
||||
reload: bool = False
|
||||
|
||||
|
||||
class GitConfig(BaseModel):
|
||||
|
|
@ -43,6 +43,9 @@ class Settings(BaseSettings):
|
|||
if p.exists():
|
||||
with p.open("r", encoding="utf-8") as fh:
|
||||
data = yaml.safe_load(fh) or {}
|
||||
else:
|
||||
import sys, os
|
||||
sys.stderr.write(f"Warning: Config file {os.path.realpath(p)} not found.\n")
|
||||
return cls(**data)
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
from injectable import inject
|
||||
|
||||
from app.core.woodpecker import Woodpecker
|
||||
from core.woodpecker import Woodpecker
|
||||
|
||||
|
||||
class AutowireSupport:
|
||||
|
|
@ -6,10 +6,10 @@ from typing import Annotated
|
|||
|
||||
from injectable import injectable, Autowired, autowired
|
||||
|
||||
from app.config import get_settings
|
||||
from app.model.webhook import WoodpeckerEvent
|
||||
from app.services import GitService, DockerService
|
||||
from app.services.mo import Mo
|
||||
from config import get_settings
|
||||
from model.webhook import WoodpeckerEvent
|
||||
from services import GitService, DockerService
|
||||
from services.mo import Mo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -3,8 +3,8 @@ import logging
|
|||
from fastapi import FastAPI
|
||||
from injectable import load_injection_container
|
||||
|
||||
from app.config import get_settings
|
||||
from app.util.logging import HandlerFactory
|
||||
from config import get_settings
|
||||
from util.logging import HandlerFactory
|
||||
|
||||
|
||||
class KarlApplication:
|
||||
|
|
@ -45,13 +45,13 @@ class KarlApplication:
|
|||
logging_logger.propagate = False
|
||||
|
||||
def _set_middlewares(self, app: FastAPI):
|
||||
from app.web.middlewares import LoggingMiddleware
|
||||
from web.middlewares import LoggingMiddleware
|
||||
app.add_middleware(LoggingMiddleware)
|
||||
|
||||
def _set_routes(self, app: FastAPI):
|
||||
from app.core.router import router as core_router
|
||||
from core.router import router as core_router
|
||||
app.include_router(core_router)
|
||||
from app.api.v1 import router as api_v1_router
|
||||
from api.v1 import router as api_v1_router
|
||||
app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"])
|
||||
pass
|
||||
|
||||
|
|
@ -61,17 +61,3 @@ class KarlApplication:
|
|||
|
||||
def run():
|
||||
return KarlApplication()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
settings = get_settings()
|
||||
uvicorn.run(
|
||||
"app.main:run",
|
||||
factory=True,
|
||||
host=settings.app.host,
|
||||
port=settings.app.port,
|
||||
reload=settings.app.reload,
|
||||
log_config=None,
|
||||
)
|
||||
|
|
@ -5,7 +5,7 @@ import docker
|
|||
from docker.models.containers import Container
|
||||
from injectable import injectable
|
||||
|
||||
from app.model.containers import Tree, Compose, SimpleContainer
|
||||
from model.containers import Tree, Compose, SimpleContainer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ class DockerService:
|
|||
return self._tree
|
||||
|
||||
def reload(self, compose_path: Path):
|
||||
# TODO: Won't work in docker container
|
||||
cmd = ["sudo", "docker", "compose", "-f", str(compose_path), "up", "-d"]
|
||||
import subprocess
|
||||
try:
|
||||
|
|
@ -4,7 +4,7 @@ from typing import Annotated
|
|||
|
||||
from injectable import injectable, autowired, Autowired
|
||||
|
||||
from app.services import Passwords
|
||||
from services import Passwords
|
||||
|
||||
|
||||
class ValueTemplate(Template):
|
||||
|
|
@ -34,8 +34,10 @@ class KeyRequest:
|
|||
@injectable(singleton=True)
|
||||
class Passwords:
|
||||
def __init__(self):
|
||||
from app.config import get_settings
|
||||
from config import get_settings
|
||||
settings = get_settings()
|
||||
import keyring_backend
|
||||
keyring.set_keyring(keyring=keyring_backend.Backend())
|
||||
|
||||
with open(settings.kp.secret, "r") as fh:
|
||||
keyring.set_password("karl", "kp", fh.read().splitlines()[0])
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from git import Repo, Remote
|
||||
from injectable import injectable
|
||||
|
||||
from app.config import GitConfig, get_settings
|
||||
from config import GitConfig, get_settings
|
||||
|
||||
|
||||
@injectable(singleton=True)
|
||||
|
|
@ -67,6 +67,12 @@ class HandlerFactory:
|
|||
return handler
|
||||
|
||||
def file_handler(prefix: str = ''):
|
||||
if not file_path:
|
||||
import sys
|
||||
sys.stderr.write("No file path specified, skipping file logging...\n")
|
||||
return None
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_path.touch(exist_ok=True)
|
||||
handler = TimedRotatingFileHandler(file_path, when='midnight', backupCount=30)
|
||||
handler.setFormatter(ApplicationFormatter(prefix))
|
||||
handler.setLevel('TRACE')
|
||||
|
|
@ -83,4 +89,5 @@ class HandlerFactory:
|
|||
handlers.append(console_handler(handler_prefix))
|
||||
case _:
|
||||
raise ValueError(f"Unknown target: {target}")
|
||||
handlers = [h for h in handlers if h is not None]
|
||||
return handlers
|
||||
0
src/karl/web/__init__.py
Normal file
0
src/karl/web/__init__.py
Normal file
|
|
@ -3,8 +3,8 @@ from pathlib import Path
|
|||
import pytest
|
||||
import yaml
|
||||
|
||||
from app.services import Passwords
|
||||
from app.services.mo import Mo
|
||||
from karl.services import Passwords
|
||||
from karl.services.mo import Mo
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue