Skip to content

Implement Zenodo release automation in GitHub Actions #6010

Implement Zenodo release automation in GitHub Actions

Implement Zenodo release automation in GitHub Actions #6010

Workflow file for this run

name: CI/CD
on:
merge_group:
pull_request:
branches:
- dev
- main
- v1
push:
branches:
- dev
- main
- v1
tags:
- 'v*'
workflow_dispatch:
workflow_run:
workflows: ['Update pre-commit hooks']
branches:
- update-pre-commit-hooks
types:
- completed
env:
FORCE_COLOR: 1
concurrency:
cancel-in-progress: ${{ !contains(fromJSON('["dev", "main", "v1"]'), github.ref_name) }}
group: ${{ github.repository }}-${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
jobs:
pre-commit:
name: Run pre-commit
runs-on: ubuntu-24.04
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
check-latest: true
python-version: '3.13'
- name: Run pre-commit
uses: pre-commit/[email protected]
code-ql:
name: CodeQL
needs:
- pre-commit
permissions:
security-events: write
runs-on: ubuntu-24.04
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: '/language:python'
test:
name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs:
- pre-commit
strategy:
matrix:
os:
- macos-latest
- ubuntu-latest
- windows-latest
python-version:
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- '3.13'
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
allow-prereleases: true
cache: pip
cache-dependency-path: |
requirements/runtime.txt
requirements/tests.txt
check-latest: true
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --requirement requirements/tests.txt
python -m pip install .
- name: Run tests
run: |
make test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
build:
name: Build distribution
needs: test
runs-on: ubuntu-24.04
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
cache: pip
cache-dependency-path: |
requirements/build.txt
check-latest: true
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements/build.txt
python -m pip install .
- name: Build distribution
run: |
make package
- name: Upload package artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
- name: Set version
if: startsWith(github.event.ref, 'refs/tags/v')
run: echo "VERSION=$(echo ${{ github.ref_name }} | sed 's/^v//')" >> $GITHUB_ENV
- name: Generate SBOM
if: startsWith(github.event.ref, 'refs/tags/v')
run: |
make sbom > holidays-${{ env.VERSION }}-sbom.json
- name: Upload SBOM
if: startsWith(github.event.ref, 'refs/tags/v')
uses: actions/upload-artifact@v4
with:
name: sbom
path: holidays-${{ env.VERSION }}-sbom.json
test-build:
name: Test build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: build
strategy:
matrix:
os:
- macos-latest
- ubuntu-latest
- windows-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
cache: pip
cache-dependency-path: |
requirements/runtime.txt
requirements/tests.txt
check-latest: true
python-version: '3.13'
- name: Get package artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Run tests
shell: bash
run: |
rm -rf holidays
python -m pip install --requirement requirements/tests.txt
python -m pip install `ls dist/*.whl`
pytest --dist loadscope --numprocesses auto tests/countries tests/financial
python -m pip uninstall -y holidays python-dateutil six
python -m pip install `ls dist/*.tar.gz`
pytest --dist loadscope --numprocesses auto tests/countries tests/financial
test-docs:
name: Test docs build
runs-on: ubuntu-24.04
needs: test
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set Up Python
uses: actions/setup-python@v5
with:
cache: pip
cache-dependency-path: requirements/docs.txt
python-version: '3.13'
- name: Install dependencies
run: |
python -m pip install --requirement requirements/docs.txt
python -m pip install .
- name: Build docs
run: |
make doc
publish-main:
name: Publish generated artifacts
if: |
github.repository == 'vacanza/holidays' &&
github.event_name == 'push' &&
startsWith(github.event.ref, 'refs/tags/v')
environment: main
needs:
- test-build
- test-docs
permissions:
contents: write
id-token: write
runs-on: ubuntu-24.04
steps:
- name: Download package artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
sign-artifacts:
name: Create SHA1 checksums and Sigstore signatures
runs-on: ubuntu-24.04
needs:
- publish-main
permissions:
id-token: write
steps:
- name: Download package artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Compute SHA1 checksums
run: |
cd dist
for file in *; do
sha1sum "$file" > "$file.sha1"
done
- name: Sign the files using Sigstore
uses: sigstore/[email protected]
with:
inputs: |
./dist/*.tar.gz
./dist/*.whl
- name: Upload package dist and signatures
uses: actions/upload-artifact@v4
with:
name: signed-artifacts
path: dist
update-github-release:
name: Update GitHub release with SBOM and signed artifacts
runs-on: ubuntu-24.04
needs:
- sign-artifacts
permissions:
contents: write
steps:
- name: Download SBOM
uses: actions/download-artifact@v4
with:
name: sbom
- name: Download package dist and signatures
uses: actions/download-artifact@v4
with:
name: signed-artifacts
path: dist
- name: Update Github release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload --repo vacanza/holidays ${{ github.ref_name }} dist/*
gh release upload --repo vacanza/holidays ${{ github.ref_name }} holidays-*-sbom.json
upload-to-zenodo:
name: Publish release to Zenodo
if: github.event_name == 'release' && github.event.action == 'published'
runs-on: ubuntu-latest
needs: [update-github-release]
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download SBOM
uses: actions/download-artifact@v4
with:
name: sbom
- name: Download signed artifacts
uses: actions/download-artifact@v4
with:
name: signed-artifacts
path: dist
- name: Prepare release archive
run: |
zip -r vacanza-holidays-${{ github.ref_name }}.zip dist/* holidays-*-sbom.json
- name: Check for existing Zenodo deposition
id: check_deposition
run: |
response=$(curl -s -H "Authorization: Bearer ${{ secrets.ZENODO_TOKEN }}" \
"https://zenodo.org/api/deposit/depositions?sort=mostrecent&q=Vacanza%20Holidays")
deposition_id=$(echo "$response" | jq -r '.[0].id // ""')
concept_doi=$(echo "$response" | jq -r '.[0].conceptdoi // ""')
if [ -n "$deposition_id" ]; then
echo "Existing deposition found: $deposition_id"
echo "DEPOSITION_ID=$deposition_id" >> $GITHUB_ENV
echo "CONCEPT_DOI=$concept_doi" >> $GITHUB_ENV
echo "NEW_VERSION=true" >> $GITHUB_ENV
else
echo "No existing deposition found. Will create a new one."
echo "NEW_VERSION=false" >> $GITHUB_ENV
fi
env:
ZENODO_TOKEN: ${{ secrets.ZENODO_TOKEN }}
- name: Create new Zenodo deposition (if no existing deposition)
id: create_deposition
if: env.NEW_VERSION == 'false'
run: |
response=$(curl -X POST \
-H "Authorization: Bearer ${{ secrets.ZENODO_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"metadata": {"title": "Vacanza Holidays ${{ github.ref_name }}", "upload_type": "software", "description": "Release ${{ github.ref_name }} of Vacanza Holidays"}}' \
https://zenodo.org/api/deposit/depositions)
if [[ $(echo "$response" | jq -r '.status // "none"') == "error" ]]; then
echo "Error creating deposition: $(echo "$response" | jq -r '.message')"
exit 1
fi
deposition_id=$(echo "$response" | jq -r '.id')
echo "DEPOSITION_ID=$deposition_id" >> $GITHUB_ENV
env:
ZENODO_TOKEN: ${{ secrets.ZENODO_TOKEN }}
- name: Create new version of existing Zenodo deposition (if deposition exists)
id: new_version
if: env.NEW_VERSION == 'true'
run: |
response=$(curl -X POST \
-H "Authorization: Bearer ${{ secrets.ZENODO_TOKEN }}" \
"https://zenodo.org/api/deposit/depositions/${{ env.DEPOSITION_ID }}/actions/newversion")
if [[ $(echo "$response" | jq -r '.status // "none"') == "error" ]]; then
echo "Error creating new version: $(echo "$response" | jq -r '.message')"
exit 1
fi
new_deposition_id=$(echo "$response" | jq -r '.links.latest_draft' | awk -F'/' '{print $NF}')
echo "NEW_DEPOSITION_ID=$new_deposition_id" >> $GITHUB_ENV
env:
ZENODO_TOKEN: ${{ secrets.ZENODO_TOKEN }}
- name: Update metadata for new version (if applicable)
if: env.NEW_VERSION == 'true'
run: |
curl -X PUT \
-H "Authorization: Bearer ${{ secrets.ZENODO_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"metadata": {"title": "Vacanza Holidays ${{ github.ref_name }}", "upload_type": "software", "description": "Release ${{ github.ref_name }} of Vacanza Holidays"}}' \
https://zenodo.org/api/deposit/depositions/${{ env.NEW_DEPOSITION_ID }}
env:
ZENODO_TOKEN: ${{ secrets.ZENODO_TOKEN }}
- name: Upload files to Zenodo
run: |
deposition_id_to_use=${{ env.NEW_VERSION == 'true' && env.NEW_DEPOSITION_ID || env.DEPOSITION_ID }}
curl -X POST \
-H "Authorization: Bearer ${{ secrets.ZENODO_TOKEN }}" \
-F "file=@vacanza-holidays-${{ github.ref_name }}.zip" \
https://zenodo.org/api/deposit/depositions/$deposition_id_to_use/files
- name: Publish Zenodo deposition
run: |
deposition_id_to_use=${{ env.NEW_VERSION == 'true' && env.NEW_DEPOSITION_ID || env.DEPOSITION_ID }}
curl -X POST \
-H "Authorization: Bearer ${{ secrets.ZENODO_TOKEN }}" \
https://zenodo.org/api/deposit/depositions/$deposition_id_to_use/actions/publish
- name: Output DOI
run: |
deposition_id_to_use=${{ env.NEW_VERSION == 'true' && env.NEW_DEPOSITION_ID || env.DEPOSITION_ID }}
response=$(curl -s -H "Authorization: Bearer ${{ secrets.ZENODO_TOKEN }}" \
https://zenodo.org/api/deposit/depositions/$deposition_id_to_use)
doi=$(echo "$response" | jq -r '.doi')
echo "Published DOI: $doi"