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
124 changes: 0 additions & 124 deletions .github/dependabot.yml

This file was deleted.

50 changes: 50 additions & 0 deletions .github/workflows/attestation-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Attestation Health Check

on:
schedule:
- cron: '23 3 * * 1' # Weekly Monday 3:23am UTC
workflow_dispatch:

jobs:
verify:
name: Verify Latest Release Attestations
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Get latest release tag
id: release
run: |
TAG=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "")
if [ -z "$TAG" ]; then
echo "No releases found, skipping"
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Verify attestations
if: steps.release.outputs.skip != 'true'
run: |
echo "Verifying attestations for ${{ steps.release.outputs.tag }}"
gh attestation list --repo ${{ github.repository }} --limit 1 | grep -q "." || {
echo "::error::No attestations found for latest release"
exit 1
}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Open issue on failure
if: failure()
run: |
gh issue create \
--repo ${{ github.repository }} \
--title "Attestation verification failed for ${{ steps.release.outputs.tag }}" \
--body "Weekly attestation health check failed. Verify that the release workflow produced valid attestations." \
--label "bug"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 changes: 71 additions & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,79 @@ jobs:
name: sdist
path: dist

attest:
name: Attest Build Provenance and SBOM
needs: [release-please, build-wheels, build-sdist]
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
attestations: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: wheels-*
merge-multiple: true
path: dist

- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: sdist
path: dist

- name: Attest Build Provenance
uses: actions/attest-build-provenance@96b4a1ef7235a096b17240c259729fdd70c83d45 # v2
with:
subject-path: dist/*
continue-on-error: true

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ needs.release-please.outputs.tag_name }}

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

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Generate Python SBOM
run: |
pip install cyclonedx-bom
cyclonedx-py requirements --outfile python-sbom.cdx.json --format json
continue-on-error: true

- name: Generate Rust SBOM
run: |
cargo install cargo-sbom --locked
cd rust && cargo sbom --output-format cyclonedx_json_v1_6 > ../rust-sbom.cdx.json
continue-on-error: true

- name: Merge SBOMs
run: |
pip install cyclonedx-cli || true
# If cyclonedx-cli merge is available, merge; otherwise use the Python SBOM
if command -v cyclonedx &> /dev/null; then
cyclonedx merge --input-files python-sbom.cdx.json rust-sbom.cdx.json --output-file sbom.cdx.json
else
# Fallback: use Python SBOM (Rust SBOM still attested separately if needed)
cp python-sbom.cdx.json sbom.cdx.json
fi
continue-on-error: true

- name: Attest SBOM
uses: actions/attest-sbom@10926c72720ffc3f7b666661c8e55b1344e2a365 # v2
with:
subject-path: dist/*
sbom-path: sbom.cdx.json
continue-on-error: true

publish:
name: Publish to PyPI
needs: [release-please, build-wheels, build-sdist]
needs: [release-please, build-wheels, build-sdist, attest]
runs-on: ubuntu-latest
environment: release
permissions:
Expand Down
4 changes: 4 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>cachekit-io/renovate-config"]
}
29 changes: 16 additions & 13 deletions tests/unit/backends/test_file_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,19 +809,21 @@ def test_get_expired_ttl_deletes_file(self, backend: FileBackend, config: FileBa
"""Test get deletes expired files."""
key = "expired_key"
value = b"expired_value"
now = time.time()

# Set with 1 second TTL
# Set with 1 second TTL at real time
backend.set(key, value, ttl=1)

# Verify it exists
assert backend.get(key) == value

# Wait for expiration
time.sleep(1.5)
# Advance time past TTL instead of sleeping (no CI flake)
import unittest.mock

# get should return None and delete the expired file
result = backend.get(key)
assert result is None
with unittest.mock.patch("cachekit.backends.file.backend.time") as mock_time:
mock_time.time.return_value = now + 10 # 10s in the future
result = backend.get(key)
assert result is None

# File should be deleted
file_path = backend._key_to_path(key)
Expand Down Expand Up @@ -935,19 +937,20 @@ def test_exists_expired_ttl_deletes_file(self, backend: FileBackend, config: Fil
"""Test exists returns False and deletes expired file."""
key = "exists_expired"
value = b"value"
now = time.time()

# Set with 2 second TTL (1s too tight under CI load)
backend.set(key, value, ttl=2)
backend.set(key, value, ttl=1)

# Verify it exists
assert backend.exists(key) is True

# Wait for expiration
time.sleep(2.5)
# Advance time past TTL instead of sleeping (no CI flake)
import unittest.mock

# exists should return False and delete the file
result = backend.exists(key)
assert result is False
with unittest.mock.patch("cachekit.backends.file.backend.time") as mock_time:
mock_time.time.return_value = now + 10
result = backend.exists(key)
assert result is False

file_path = backend._key_to_path(key)
assert not os.path.exists(file_path)
Expand Down
Loading