Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 0 additions & 30 deletions .github/scripts/repack.sh

This file was deleted.

22 changes: 15 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
python-version: ${{ matrix.python-version }}

- name: Run tests
run: uvx tox -e ${{ matrix.toxenv }}
run: uvx --from tox-uv tox -e ${{ matrix.toxenv }}

- uses: codecov/codecov-action@v5
with:
Expand Down Expand Up @@ -123,16 +123,16 @@ jobs:
- name: Write SCM version
id: scm_version
run: |
pipx install setuptools_scm
echo "scm_version=$(setuptools-scm)" >> "$GITHUB_OUTPUT"
pipx install hatch
echo "scm_version=$(hatch version)" >> "$GITHUB_OUTPUT"

- name: Build and export
uses: docker/build-push-action@v6
with:
tags: mozillasecurity/fuzzmanager:latest
outputs: type=docker,dest=${{ runner.temp }}/fuzzmanager.tar
build-args: |
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_FuzzManager=${{ steps.scm_version.outputs.scm_version }}
SETUPTOOLS_SCM_PRETEND_VERSION=${{ steps.scm_version.outputs.scm_version }}

- name: Upload artifact
uses: actions/upload-artifact@v5
Expand Down Expand Up @@ -187,10 +187,18 @@ jobs:
python-version: "3.12"

- name: Build Python distributions
run: uvx --from build pyproject-build
run: uv build

- name: Repack Python distributions
run: bash .github/scripts/repack.sh
- name: Strip git URLs from sdist (PyPI requirement)
run: |
WORK="$(mktemp -d)"
TAR="$(ls dist/*.tar.*)"
tar -C "$WORK" -xvaf "$TAR"
sed -Ei 's/^(Requires-Dist:\s*[A-Za-z0-9][A-Za-z0-9._-]*)\s*@\s*([^ ]+)(.*)$/\1\3/' "$WORK"/*/PKG-INFO
FN="$(ls "$WORK")"
rm "$TAR"
tar -C "$WORK" -cvaf "$TAR" "$FN"
rm -rf "$WORK"

- name: Check Python distributions
run: uvx twine check dist/*
Expand Down
45 changes: 17 additions & 28 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,34 @@ WORKDIR /src
RUN npm install
RUN npm run production

FROM python:3.11-alpine AS backend

RUN apk add --no-cache build-base git linux-headers mariadb-dev

# Install dependencies before copying src, so pip is only run when needed
# Also install build dependencies that are otherwise missed in wheel cache
# (from pyproject.toml [build-system].requires)
COPY ./requirements.txt ./setup.cfg /src/
RUN cd /src && \
pip install -U setuptools && \
python -c "from setuptools.config import read_configuration as C; from itertools import chain; o=C('setup.cfg')['options']; ex=o['extras_require']; print('\0'.join(chain(o['install_requires'], ex['docker'], ex['sentry'], ex['server'], ex['taskmanager'])))" | xargs -0 pip wheel -q -c requirements.txt --wheel-dir /var/cache/wheels && \
pip wheel -q --wheel-dir /var/cache/wheels wheel setuptools_scm[toml]

FROM python:3.11-alpine

ARG SETUPTOOLS_SCM_PRETEND_VERSION_FOR_FuzzManager
ENV SETUPTOOLS_SCM_PRETEND_VERSION_FOR_FuzzManager=$SETUPTOOLS_SCM_PRETEND_VERSION_FOR_FuzzManager
ARG SETUPTOOLS_SCM_PRETEND_VERSION
ENV SETUPTOOLS_SCM_PRETEND_VERSION=$SETUPTOOLS_SCM_PRETEND_VERSION

# Install system dependencies
RUN adduser -D worker && \
apk add --no-cache bash git mariadb-client mariadb-connector-c openssh-client-default && \
rm -rf /var/log/*
apk add --no-cache bash git mariadb-client mariadb-connector-c openssh-client-default && \
rm -rf /var/log/*

COPY --from=backend /var/cache/wheels /var/cache/wheels

COPY ./requirements.txt ./setup.cfg /src/
USER worker
RUN cd /src && \
python -c "from setuptools.config import read_configuration as C; from itertools import chain; o=C('setup.cfg')['options']; ex=o['extras_require']; print('\0'.join(chain(o['install_requires'], ex['docker'], ex['sentry'], ex['server'], ex['taskmanager'])))" | xargs -0 pip install --no-cache-dir --no-index --find-links /var/cache/wheels -q -c requirements.txt
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

# Embed full source code
USER root
COPY . /src/

# Retrieve previous Javascript build
COPY --from=frontend /src/dist/ /src/server/frontend/dist/

# Install build dependencies, install FuzzManager, then clean up in single layer
RUN apk add --no-cache --virtual .build-deps build-base linux-headers mariadb-dev && \
cd /src && \
UV_PROJECT_ENVIRONMENT=/usr/local uv sync --frozen --extra docker --extra sentry --extra server --extra taskmanager --no-dev && \
uv cache clean && \
rm -rf /root/.cache/uv && \
apk del .build-deps && \
rm -rf /var/log/*

# Setup directories and permissions
RUN mkdir -p \
/data/fuzzing-tc-config \
/data/crashes \
Expand All @@ -57,12 +50,8 @@ RUN mkdir -p \
/data/repos \
/data/userdata

# Install FM
# Note: the extras must be duplicated above in the Python
# script to pre-install dependencies.
USER worker
ENV PATH="${PATH}:/home/worker/.local/bin"
RUN pip install --no-cache-dir --no-index --find-links /var/cache/wheels --no-deps -q /src[docker,sentry,server,taskmanager]
RUN mkdir -m 0700 /home/worker/.ssh && cp /src/misc/sshconfig /home/worker/.ssh/config && ssh-keyscan github.com > /home/worker/.ssh/known_hosts

# Use a custom settings file that can be overwritten
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,21 @@ FuzzManager server instance or use FTB locally.
The server part of FuzzManager is a Django application. Please note that it
requires the full repository to be checked out, not just the server directory.

Dependency constraints are listed in [requirements.txt](requirements.txt). You can ask pip to respect these contraints by installing FuzzManager using:
For reproducible server installations, use [uv](https://github.com/astral-sh/uv) with the lockfile:

```pip install -c requirements.txt '.[server]'```
```bash
# Install uv if needed
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install with locked dependencies
uv sync --extra server --extra docker
```

For a basic pip installation without locked versions:

```bash
pip install '.[server]'
```

A [Redis](https://redis.io/) server is also required for EC2SpotManager and API rate limiting, and can be installed on a Debian-based Linux with:

Expand Down
159 changes: 156 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,153 @@
[build-system]
requires = ["setuptools >= 43", "wheel", "setuptools_scm[toml] >= 3.4"]
build-backend = "setuptools.build_meta"
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[project]
name = "FuzzManager"
dynamic = ["version"]
description = "A fuzzing management tools collection"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MPL-2.0"}
authors = [
{name = "Christian Holler"},
]
maintainers = [
{name = "Mozilla Fuzzing Team", email = "fuzzing@mozilla.com"},
]
keywords = ["fuzz", "fuzzing", "security", "test", "testing"]
classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Testing",
"Topic :: Security",
]

# Core client dependencies (flexible ranges)
dependencies = [
"fasteners>=0.14.1",
"requests>=2.20.1",
]

[project.optional-dependencies]
# Server dependencies (flexible compatible release ranges)
server = [
"celery~=5.3.5",
"crispy-bootstrap3",
"django~=4.2.7",
"django-crispy-forms~=2.1",
"django-enumfields~=2.1.1",
"django-notifications-hq~=1.8.3",
"djangorestframework~=3.15.1",
"redis[hiredis]",
"whitenoise~=6.9",
]

# EC2 spot manager dependencies
ec2spotmanager = [
"boto3",
"django-chartjs~=2.3.0",
"laniakea",
"pyyaml",
]

# Task manager dependencies
# Note: fuzzing-decision is a git dependency (see [tool.uv.sources])
# uv will install it automatically, pip users need manual install
taskmanager = [
"fuzzing-decision",
"kombu",
]

# Docker/production dependencies
docker = [
"gunicorn~=23.0.0",
"mercurial",
"mozilla-django-oidc~=4.0.1",
"mysqlclient~=2.2.4",
]

# Sentry integration
# Note: sentry-fuzzing-config is a git dependency (see [tool.uv.sources])
# uv will install it automatically, pip users need manual install
sentry = [
"sentry-fuzzing-config",
]

# Test dependencies
test = [
"fakeredis>=2.20.0; python_version <= '3.11'",
"pytest",
"pytest-cov",
"pytest-django; python_version <= '3.11'",
"pytest-mock",
]

# Development dependencies
dev = [
"pre-commit",
"tox",
"tox-uv",
]

[project.scripts]
collector = "Collector:Collector.main"
cov-reporter = "CovReporter:CovReporter.main"
ec2-reporter = "EC2Reporter:EC2Reporter.main"
task-status-reporter = "TaskStatusReporter:TaskStatusReporter.main"

[project.urls]
Homepage = "https://github.com/MozillaSecurity/FuzzManager"
Repository = "https://github.com/MozillaSecurity/FuzzManager"
Issues = "https://github.com/MozillaSecurity/FuzzManager/issues"

# Hatchling build configuration
[tool.hatch.version]
source = "vcs"

[tool.hatch.build]
packages = [
"Collector",
"CovReporter",
"EC2Reporter",
"FTB",
"Reporter",
"TaskStatusReporter",
]

# Hatchling properly handles extras in wheels - no manual stripping needed!
[tool.hatch.build.targets.wheel]
packages = [
"Collector",
"CovReporter",
"EC2Reporter",
"FTB",
"Reporter",
"TaskStatusReporter",
]

[tool.hatch.build.targets.sdist]
exclude = [
"/.github",
"/.tox",
"/dist",
"*.pyc",
"__pycache__",
]

# Black configuration
[tool.black]
target-version = ["py310"]

# Coverage configuration
[tool.coverage.report]
exclude_lines = [
"@(abc.)?abstract*",
Expand All @@ -22,6 +165,7 @@ omit = [
"*/.eggs/*",
]

# Isort configuration
[tool.isort]
known_first_party = [
"Collector",
Expand All @@ -37,6 +181,7 @@ known_first_party = [
]
profile = "black"

# Pytest configuration
[tool.pytest.ini_options]
log_level = "DEBUG"
DJANGO_SETTINGS_MODULE = "server.settings_test"
Expand All @@ -54,4 +199,12 @@ norecursedirs = [
"dist",
]

[tool.setuptools_scm]
# UV configuration
[tool.uv]
# Git dependencies are kept in sources for development
# They are not included in published packages (PyPI requirement)

[tool.uv.sources]
# Keep git dependencies separate - these won't be in published packages
fuzzing-decision = { git = "https://github.com/MozillaSecurity/orion", subdirectory = "services/fuzzing-decision" }
sentry-fuzzing-config = { git = "https://github.com/MozillaSecurity/sentry" }
Loading