Skip to content

Commit b4670ad

Browse files
committed
squash version 0.1
0 parents  commit b4670ad

41 files changed

Lines changed: 8496 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/push-image.yaml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: Docker Image
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
tags:
8+
- "v*"
9+
workflow_dispatch:
10+
11+
permissions: {}
12+
13+
env:
14+
REGISTRY: ghcr.io
15+
FULL_IMAGE_NAME: ghcr.io/${{ github.repository }}
16+
17+
jobs:
18+
push-image:
19+
name: Build and push image
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
packages: write
24+
25+
steps:
26+
- name: Checkout repository
27+
uses: actions/checkout@v4
28+
with:
29+
fetch-depth: 0
30+
31+
- name: Set up Docker Buildx
32+
uses: docker/setup-buildx-action@v3
33+
34+
- name: Log in to GitHub Container Registry
35+
uses: docker/login-action@v3
36+
with:
37+
registry: ${{ env.REGISTRY }}
38+
username: ${{ github.repository_owner }}
39+
password: ${{ secrets.GITHUB_TOKEN }}
40+
41+
- name: Extract metadata for Docker
42+
id: meta
43+
uses: docker/metadata-action@v5
44+
with:
45+
images: ${{ env.FULL_IMAGE_NAME }}
46+
tags: |
47+
type=ref,event=branch
48+
type=ref,event=tag
49+
type=semver,pattern={{version}}
50+
type=semver,pattern={{major}}.{{minor}}
51+
type=sha,prefix=
52+
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
53+
54+
- name: Build and push Docker image
55+
uses: docker/build-push-action@v5
56+
with:
57+
context: .
58+
file: ./Dockerfile
59+
push: true
60+
tags: ${{ steps.meta.outputs.tags }}
61+
labels: ${{ steps.meta.outputs.labels }}
62+
63+
deploy-to-dev:
64+
name: Deploy to dev
65+
runs-on: ubuntu-latest
66+
needs: push-image
67+
if: github.ref == 'refs/heads/main'
68+
environment:
69+
name: dev
70+
url: ${{ vars.APP_URL }}
71+
72+
steps:
73+
- name: Deploy via SSH
74+
uses: appleboy/ssh-action@v1.2.4
75+
with:
76+
host: ${{ secrets.SSH_HOST }}
77+
username: ${{ secrets.SSH_USER }}
78+
key: ${{ secrets.SSH_PRIVATE_KEY }}
79+
port: ${{ secrets.SSH_PORT || 22 }}
80+
fingerprint: ${{ secrets.SSH_HOST_FINGERPRINT }}
81+
script: deploy dev

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.env
2+
ssh_key
3+
known_hosts
4+
config/*
5+
!config/*.example.yaml
6+
compose.yml
7+
CLAUDE.md
8+
.claude/
9+
.venv/
10+
__pycache__/
11+
*.egg-info/
12+
*.pyc
13+
.pytest_cache/
14+
.ruff_cache/

.pre-commit-config.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
ci:
2+
autoupdate_schedule: quarterly
3+
4+
repos:
5+
- repo: https://github.com/pre-commit/pre-commit-hooks
6+
rev: v6.0.0
7+
hooks:
8+
- id: check-added-large-files
9+
args: ["--maxkb=2000"]
10+
- id: trailing-whitespace
11+
- id: end-of-file-fixer
12+
- id: check-yaml
13+
- id: check-json
14+
- id: check-toml
15+
- id: check-merge-conflict
16+
- id: check-case-conflict
17+
- id: mixed-line-ending
18+
19+
# Python - Ruff
20+
- repo: https://github.com/astral-sh/ruff-pre-commit
21+
rev: v0.14.10
22+
hooks:
23+
# Run the linter
24+
- id: ruff
25+
args: [--fix]
26+
# Run the formatter
27+
- id: ruff-format
28+
29+
# Find common spelling mistakes in comments and docstrings
30+
- repo: https://github.com/codespell-project/codespell
31+
rev: v2.4.1
32+
hooks:
33+
- id: codespell
34+
args: ['--ignore-regex=(\b[A-Z]+\b)', '--ignore-words-list=ue']
35+
types_or: [python, rst, markdown]
36+
37+
# Do YAML formatting
38+
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
39+
rev: v2.15.0
40+
hooks:
41+
- id: pretty-format-yaml
42+
args: [--autofix, --indent, '2', --preserve-quotes]
43+
44+
# Detect secrets
45+
- repo: https://github.com/gitleaks/gitleaks
46+
rev: v8.30.0
47+
hooks:
48+
- id: gitleaks

Dockerfile

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# syntax=docker/dockerfile:1
2+
3+
# Stage 1: Builder
4+
FROM python:3.13-slim AS builder
5+
6+
WORKDIR /app
7+
8+
RUN apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates && rm -rf /var/lib/apt/lists/*
9+
10+
ADD https://astral.sh/uv/install.sh /uv-installer.sh
11+
RUN sh /uv-installer.sh && rm /uv-installer.sh
12+
13+
ENV PATH="/root/.local/bin:$PATH"
14+
15+
COPY pyproject.toml uv.lock ./
16+
COPY .git/ .git/
17+
COPY app/ app/
18+
19+
RUN uv sync --frozen --no-dev --all-extras
20+
21+
# Stage 2: Runtime
22+
FROM python:3.13-slim
23+
24+
WORKDIR /app
25+
26+
RUN apt-get update && apt-get install -y --no-install-recommends sudo curl ca-certificates && rm -rf /var/lib/apt/lists/* && \
27+
groupadd -r appuser -g 1000 && \
28+
useradd -r -u 1000 -g appuser -m -s /bin/bash appuser && \
29+
echo "appuser ALL=(ALL) NOPASSWD: /usr/bin/apt-get" >> /etc/sudoers.d/appuser && \
30+
mkdir -p /data && \
31+
chown -R appuser:appuser /data
32+
33+
COPY --from=builder --chown=appuser:appuser /root/.local/bin/uv /usr/local/bin/uv
34+
COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv
35+
COPY --from=builder --chown=appuser:appuser /app/app /app/app
36+
COPY --chown=appuser:appuser pyproject.toml uv.lock ./
37+
COPY --chown=appuser:appuser entrypoint.sh /app/entrypoint.sh
38+
39+
USER appuser
40+
41+
ENV PATH="/app/.venv/bin:$PATH"
42+
43+
EXPOSE 8000
44+
45+
ENTRYPOINT ["/app/entrypoint.sh"]
46+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# snakedispatch
2+
3+
Dispatches Snakemake workflows to compute backends.
4+
5+
## Architecture
6+
7+
- **Single-tenant service**: all API routes are trusted; auth is enforced at the network layer.
8+
- **Compute backends** (local, slurm_ssh) are abstracted behind `ComputeBackend` (`app/backends/base.py`). The backend is selected at startup from `config.yaml` and injected via FastAPI DI.
9+
- **Job lifecycle**: `PENDING` -> `SETUP` -> `RUNNING` -> `COMPLETED`/`FAILED`/`CANCELLED`. Orchestrated by `execute_job` in `app/tasks.py`; state persisted via `app/store.py`.
10+
- **snkmt DB sync**: Snakemake job-graph metadata (rule counts, file listings) is synced from the compute backend to a local SQLite file during execution and exposed via `/snkmt/*` endpoints. See `app/snkmt.py` for the query layer.

app/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""snakedispatch - FastAPI microservice for dispatching workflows."""

app/backends/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from app.backends.local import LocalBackend
6+
from app.config import LocalConfig, SlurmSSHConfig
7+
8+
if TYPE_CHECKING:
9+
from app.backends.base import ComputeBackend
10+
11+
12+
def create_backend(config: SlurmSSHConfig | LocalConfig) -> ComputeBackend:
13+
"""Create the appropriate backend from config."""
14+
if isinstance(config, LocalConfig):
15+
return LocalBackend(config)
16+
from app.backends.slurm_ssh import ( # noqa: PLC0415, I001 # requires [slurm]
17+
SlurmSSHBackend,
18+
)
19+
20+
return SlurmSSHBackend(config)

0 commit comments

Comments
 (0)