diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 212076d..bd9600e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,12 +3,6 @@ name: Publish to PyPI on: release: types: [published] - push: - branches: [ main ] - paths: - - 'setup.py' - - 'pyproject.toml' - - 's3syncy/**' jobs: deploy: @@ -18,10 +12,10 @@ jobs: id-token: write # Required for OIDC steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: "3.10" diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 188208c..8751146 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -24,30 +24,33 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} queries: security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{ matrix.language }}" trivy-scan: name: Trivy Vulnerability Scan runs-on: ubuntu-latest - + permissions: + contents: read + security-events: write + steps: - name: Checkout repository - uses: actions/checkout@v4 - + uses: actions/checkout@v6 + - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: @@ -56,10 +59,10 @@ jobs: format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - + - name: Upload Trivy results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v3 - if: always() + uses: github/codeql-action/upload-sarif@v4 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository with: sarif_file: 'trivy-results.sarif' category: 'trivy' @@ -67,41 +70,41 @@ jobs: dependency-check: name: Dependency Check runs-on: ubuntu-latest - + steps: - name: Checkout repository - uses: actions/checkout@v4 - + uses: actions/checkout@v6 + - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: "3.10" - + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - + - name: Check for vulnerable dependencies run: | pip install safety pip-audit - echo "=== Safety Check ===" + echo "=== Safety Check ===" safety check --json || true - echo "=== Pip Audit Check ===" + echo "=== Pip Audit Check ===" pip-audit --desc || true security-headers: name: Repository Security Check runs-on: ubuntu-latest - + steps: - name: Checkout repository - uses: actions/checkout@v4 - + uses: actions/checkout@v6 + - name: Check for secrets uses: trufflesecurity/trufflehog@main with: path: ./ - base: ${{ github.event.repository.default_branch }} - head: HEAD - extra_kwargs: --debug --only-verified + base: ${{ github.event.before }} + head: ${{ github.sha }} + extra_args: --debug --only-verified diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 0000000..ea79aa2 --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,40 @@ +name: Sonar Analysis + +on: + pull_request: + branches: [ main ] + types: [opened, synchronize, reopened] + push: + branches: [ main ] + workflow_dispatch: + +jobs: + sonar: + name: Build and Analyze + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -e . + + - name: Run Sonar scan + uses: SonarSource/sonarqube-scan-action@v6 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6fd620e..af8cab0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,10 +13,10 @@ jobs: python-version: ["3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -45,10 +45,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: "3.10" @@ -65,7 +65,7 @@ jobs: - name: Upload Bandit report if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: bandit-security-report path: bandit-report.json @@ -75,10 +75,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: "3.10" @@ -96,7 +96,7 @@ jobs: twine check dist/* - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: dist path: dist/ diff --git a/s3syncy/daemon.py b/s3syncy/daemon.py index aac61c4..3be2309 100644 --- a/s3syncy/daemon.py +++ b/s3syncy/daemon.py @@ -155,8 +155,11 @@ def run(self) -> None: self._wait_for_next_scan(scan_interval) except KeyboardInterrupt: - pass + log.info("KeyboardInterrupt received — initiating graceful shutdown") + self._shutdown_event.set() finally: + # Ensure shutdown event is always set so background threads can observe shutdown + self._shutdown_event.set() self._graceful_shutdown() def stop(self) -> None: @@ -351,8 +354,8 @@ def _remove_pid_file(self) -> None: return try: self.pid_file.unlink(missing_ok=True) - except OSError: - pass + except OSError as exc: + log.warning("Failed to remove PID file %s: %s", self.pid_file, exc) def _write_state(self, status: str, extra: dict[str, Any] | None = None) -> None: payload: dict[str, Any] = { diff --git a/s3syncy/engine.py b/s3syncy/engine.py index 33cdecb..e50a7ea 100644 --- a/s3syncy/engine.py +++ b/s3syncy/engine.py @@ -6,9 +6,8 @@ import os import threading from concurrent.futures import ThreadPoolExecutor, as_completed -from datetime import datetime, timezone from pathlib import Path -from typing import Dict, List, Optional, Set +from typing import Dict, Optional, Set import boto3 from botocore.config import Config as BotoConfig diff --git a/s3syncy/index.py b/s3syncy/index.py index 1d6214b..61ef0d7 100644 --- a/s3syncy/index.py +++ b/s3syncy/index.py @@ -14,7 +14,7 @@ from contextlib import contextmanager from datetime import datetime, timezone from pathlib import Path -from typing import Generator, List, Optional, Tuple +from typing import Generator, List, Optional log = logging.getLogger(__name__) diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..9e9140b --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1 @@ +sonar.projectKey=mtahle_s3syncy_b03f9071-c443-4d24-8a6a-661ff0c584a1 \ No newline at end of file