Skip to content
Merged
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
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.git
.github
.venv
.pytest_cache
__pycache__/
*.py[cod]
*.egg-info/
build/
dist/
docs/
grid/
notebooks/
outputs/
ppt/
src/
tests/__pycache__/
tutorials/
162 changes: 92 additions & 70 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,58 @@
# CI/CD – Docker build, test, and publish
# ──────────────────────────────────────────────────────────────
# Triggers:
# • Pushes to main that touch Docker-related files → build + test + push :latest
# • Manual dispatch from main → build + test + optional push
# • Pull requests touching Docker/runtime files → build + smoke test
# • Pushes to main touching Docker/runtime files → build + smoke test + push tested image
# • Manual dispatch → build + smoke test + optional push of tested image
# ──────────────────────────────────────────────────────────────

name: Docker Build & Publish

on:
pull_request:
paths:
- "Dockerfile"
- "docker-compose.yml"
- ".dockerignore"
- "entrypoint.sh"
- "start-jupyter.sh"
- "pyproject.toml"
- "sarpyx/**"
- "tests/**"
- "support/**"
- ".github/workflows/docker.yml"
push:
branches: [main]
paths:
- "Dockerfile"
- "docker-compose.yml"
- ".dockerignore"
- "entrypoint.sh"
- "start-jupyter.sh"
- "sarpyx/**"
- "tests/**"
- "pyproject.toml"
- "support/**"
- ".github/workflows/docker.yml"
workflow_dispatch:
inputs:
push_image:
description: "Push image to Docker Hub after build?"
required: false
default: "false"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
DOCKER_IMAGE: sirbastiano94/sarpyx
DOCKER_TEST_GRID: /workspace/grid/grid_smoke.geojson

jobs:
# ──────────────────── Build & Test ────────────────────────
build-and-test:
docker:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
timeout-minutes: 260
timeout-minutes: 180

steps:
- name: Checkout repository
Expand All @@ -44,84 +64,86 @@ jobs:
with:
version: v0.11.2

- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile', 'pyproject.toml') }}
restore-keys: |
${{ runner.os }}-buildx-

- name: Build Docker image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
load: true
platforms: linux/amd64
tags: ${{ env.DOCKER_IMAGE }}:ci
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
cache-from: type=gha,scope=sarpyx-docker
cache-to: type=gha,mode=max,scope=sarpyx-docker

# - name: Run Docker build tests (disabled)
# run: |
# docker run --rm ${{ env.DOCKER_IMAGE }}:ci sh -c "\
# python3.11 -m pip install pytest && \
# python3.11 -m pytest /workspace/tests/test_docker.py -v --tb=short"

- name: Move cache # prevents ever-growing cache
- name: Run mounted Docker smoke tests
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache

# ──────────────────── Publish ─────────────────────────────
publish:
needs: build-and-test
runs-on: ubuntu-latest
if: >-
github.ref == 'refs/heads/main' &&
(
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true')
)
timeout-minutes: 60

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
version: v0.11.2
docker run --rm \
-e GRID_PATH="${GRID_PATH}" \
-v "${GITHUB_WORKSPACE}/tests:/opt/smoke-tests:ro" \
-v "${GITHUB_WORKSPACE}/tests/fixtures:/workspace/grid:ro" \
${{ env.DOCKER_IMAGE }}:ci \
sh -lc "python3.11 -m pip install --no-cache-dir pytest==8.4.0 && python3.11 -m pytest /opt/smoke-tests/test_docker.py -q --tb=short"
env:
GRID_PATH: ${{ env.DOCKER_TEST_GRID }}
shell: bash

- name: Start Jupyter smoke container
run: |
docker run -d --rm \
--name sarpyx-jupyter-smoke \
-p 127.0.0.1:8888:8888 \
-e GRID_PATH="${GRID_PATH}" \
-e JUPYTER_TOKEN=ci-smoke-token \
-v "${GITHUB_WORKSPACE}/tests/fixtures:/workspace/grid:ro" \
${{ env.DOCKER_IMAGE }}:ci \
/usr/local/bin/start-jupyter.sh
env:
GRID_PATH: ${{ env.DOCKER_TEST_GRID }}
shell: bash

- name: Wait for Jupyter health check
run: |
for attempt in {1..30}; do
if curl -fsS "http://127.0.0.1:8888/lab?token=ci-smoke-token" >/dev/null; then
exit 0
fi
sleep 5
done
docker logs sarpyx-jupyter-smoke
exit 1
shell: bash

- name: Dump Jupyter smoke logs
if: always()
run: docker logs sarpyx-jupyter-smoke || true

- name: Remove Jupyter smoke container
if: always()
run: docker rm -f sarpyx-jupyter-smoke || true

- name: Log in to Docker Hub
if: >-
(
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true')
)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_IMAGE }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix=

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

- name: Move cache
- name: Tag and push tested Docker image
if: >-
(
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true')
)
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
docker tag "${{ env.DOCKER_IMAGE }}:ci" "${{ env.DOCKER_IMAGE }}:${GITHUB_SHA}"
docker push "${{ env.DOCKER_IMAGE }}:${GITHUB_SHA}"

if [ "${GITHUB_REF}" = "refs/heads/main" ]; then
docker tag "${{ env.DOCKER_IMAGE }}:ci" "${{ env.DOCKER_IMAGE }}:latest"
docker push "${{ env.DOCKER_IMAGE }}:latest"
fi
shell: bash
59 changes: 45 additions & 14 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,68 @@
# GitHub Actions workflow for publishing a Python package from main only.
name: Publish Python Package to PyPI

on:
workflow_dispatch:
push:
tags:
- "v*"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
pypi-publish:
name: PyPI Release Publication
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for trusted publishing
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Configure Python Environment
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.x"
python-version: "3.12"

- name: Install GDAL System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y gdal-bin libgdal-dev
- name: Set up uv
uses: astral-sh/setup-uv@v4

- name: Configure PDM Environment
uses: pdm-project/setup-pdm@v4
with:
python-version: "3.x"
cache: true
- name: Sync environment
run: uv sync --group dev

- name: Run tests
run: uv run pytest -q

- name: Build Python Package
run: pdm build
run: uv build

- name: Audit built wheel contents
run: |
python - <<'PY'
from pathlib import Path
import zipfile

wheels = sorted(Path("dist").glob("*.whl"))
if not wheels:
raise SystemExit("No wheel produced in dist/")

forbidden_roots = {"docs", "tests", "outputs", "pyscripts"}
with zipfile.ZipFile(wheels[0]) as wheel:
offenders = sorted(
name for name in wheel.namelist()
if name.split("/", 1)[0] in forbidden_roots
)

if offenders:
print("Forbidden wheel entries detected:")
for name in offenders:
print(name)
raise SystemExit(1)
PY

- name: Validate distributions
run: uvx twine check dist/*

- name: Upload Distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
61 changes: 61 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Python CI

on:
pull_request:
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test-build:
runs-on: ubuntu-latest
timeout-minutes: 60

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@v4

- name: Sync environment
run: uv sync --group dev

- name: Run tests
run: uv run pytest -q

- name: Build distributions
run: uv build

- name: Audit built wheel contents
run: |
python - <<'PY'
from pathlib import Path
import zipfile

wheels = sorted(Path("dist").glob("*.whl"))
if not wheels:
raise SystemExit("No wheel produced in dist/")

forbidden_roots = {"docs", "tests", "outputs", "pyscripts"}
with zipfile.ZipFile(wheels[0]) as wheel:
offenders = sorted(
name for name in wheel.namelist()
if name.split("/", 1)[0] in forbidden_roots
)

if offenders:
print("Forbidden wheel entries detected:")
for name in offenders:
print(name)
raise SystemExit(1)
PY
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ snap12/
*.csv
*.txt
*.geojson
!tests/fixtures/grid_smoke.geojson
!tests/fixtures/sentinel_smoke_grid.geojson


AGENTS.md
Expand Down
Loading
Loading