commit bdcc82807dcc905c06a1da1f4464649487054ddd Author: Marker689 Date: Sat May 9 04:32:48 2026 +0300 init: guarddog-nexus project skeleton diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1341a35 --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +NEXUS_URL=http://nexus:8081 +NEXUS_USERNAME=admin +NEXUS_PASSWORD=admin123 +NEXUS_REPOSITORIES=pypi-proxy + +DATABASE_PATH=/data/guarddog.db + +HOST=0.0.0.0 +PORT=8080 + +LOG_LEVEL=INFO +LOG_SYSLOG_HOST= +LOG_SYSLOG_PORT=514 + +WEBHOOK_SECRET= +SCAN_TIMEOUT_SECONDS=300 +TEMP_DIR=/tmp/guarddog-nexus diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a8c57a --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +__pycache__/ +*.py[cod] +*.egg-info/ +dist/ +build/ +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +data/ +*.db +.env +.venv/ +venv/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d347933 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +.PHONY: install dev test lint format typecheck docker-build docker-up docker-down clean + +install: + pip install -e . + +dev: + pip install -e ".[dev]" + +test: + pytest -v + +lint: + ruff check guarddog_nexus tests + +format: + ruff format guarddog_nexus tests + ruff check --fix guarddog_nexus tests + +typecheck: + mypy guarddog_nexus + +docker-build: + docker compose build + +docker-up: + docker compose up -d + +docker-down: + docker compose down -v + +docker-logs: + docker compose logs -f + +clean: + rm -rf dist build *.egg-info + find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true + find . -type f -name '*.pyc' -delete diff --git a/guarddog_nexus/config.py b/guarddog_nexus/config.py new file mode 100644 index 0000000..cb7bbe2 --- /dev/null +++ b/guarddog_nexus/config.py @@ -0,0 +1,34 @@ +"""Configuration via environment variables.""" + +import os +from dataclasses import dataclass, field + + +@dataclass +class Config: + nexus_url: str = os.getenv("NEXUS_URL", "http://localhost:8081") + nexus_username: str = os.getenv("NEXUS_USERNAME", "admin") + nexus_password: str = os.getenv("NEXUS_PASSWORD", "admin123") + nexus_repositories: list[str] = field(default_factory=lambda: _parse_repos()) + + database_path: str = os.getenv("DATABASE_PATH", "data/guarddog.db") + + host: str = os.getenv("HOST", "0.0.0.0") + port: int = int(os.getenv("PORT", "8080")) + + log_level: str = os.getenv("LOG_LEVEL", "INFO") + log_syslog_host: str = os.getenv("LOG_SYSLOG_HOST", "") + log_syslog_port: int = int(os.getenv("LOG_SYSLOG_PORT", "514")) + + webhook_secret: str = os.getenv("WEBHOOK_SECRET", "") + + scan_timeout_seconds: int = int(os.getenv("SCAN_TIMEOUT_SECONDS", "300")) + temp_dir: str = os.getenv("TEMP_DIR", "/tmp/guarddog-nexus") + + +def _parse_repos() -> list[str]: + raw = os.getenv("NEXUS_REPOSITORIES", "") + return [r.strip() for r in raw.split(",") if r.strip()] + + +config = Config() diff --git a/guarddog_nexus/database.py b/guarddog_nexus/database.py new file mode 100644 index 0000000..95fa759 --- /dev/null +++ b/guarddog_nexus/database.py @@ -0,0 +1,27 @@ +"""Async SQLite database setup via SQLAlchemy.""" + +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine +from sqlalchemy.orm import DeclarativeBase + +from guarddog_nexus.config import config + +DATABASE_URL = f"sqlite+aiosqlite:///{config.database_path}" + +_engine = create_async_engine(DATABASE_URL, echo=False) +_async_session = async_sessionmaker(_engine, class_=AsyncSession, expire_on_commit=False) + + +class Base(DeclarativeBase): + pass + + +async def init_db(): + import guarddog_nexus.models # noqa: F401 + + async with _engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + +async def get_session() -> AsyncSession: + async with _async_session() as session: + yield session diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..230540d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "guarddog-nexus" +version = "0.1.0" +description = "GuardDog integration with Sonatype Nexus — scan packages via webhooks" +readme = "README.md" +requires-python = ">=3.10" +license = {text = "MIT"} +dependencies = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.30.0", + "jinja2>=3.1.0", + "httpx>=0.27.0", + "sqlalchemy[asyncio]>=2.0.30", + "aiosqlite>=0.20.0", + "python-multipart>=0.0.9", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "pytest-asyncio>=0.23.0", + "pytest-httpx>=0.30.0", + "ruff>=0.4.0", + "mypy>=1.10.0", + "httpx>=0.27.0", +] + +[project.scripts] +guarddog-nexus = "guarddog_nexus.main:main" + +[tool.ruff] +target-version = "py310" +line-length = 100 +select = ["E", "F", "I", "W"] + +[tool.ruff.lint.isort] +known-first-party = ["guarddog_nexus"] + +[tool.mypy] +python_version = "3.10" +strict = true +ignore_missing_imports = true + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"]