From 01a2e654526f3ab21e614c1de25716a9d49e7edd Mon Sep 17 00:00:00 2001 From: SebastienPittet Date: Mon, 26 Oct 2026 08:54:06 +0100 Subject: [PATCH] ci: pipeline with lint, type-check, tests, security, SBOM, image build; PR size labels & badges --- .github/workflows/ci.yml | 126 +++++++++++++++++++++++++++++++ .github/workflows/pr-size.yml | 45 +++++++++++ .github/workflows/python-app.yml | 58 -------------- .github/workflows/release.yml | 67 ++++++++++++++++ README.md | 5 ++ 5 files changed, 243 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pr-size.yml delete mode 100644 .github/workflows/python-app.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..47bc54e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,126 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + PYTHON_VERSION: "3.12" + +jobs: + lint: + name: Lint & type-check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + - name: Install tooling + run: pip install -r requirements-dev.txt + - name: Ruff (lint + format check) + run: | + ruff check --output-format github . + ruff format --check . + - name: Mypy + run: mypy + - name: Actionlint (workflow self-check) + run: docker run --rm -v "$PWD:/repo" ghcr.io/rhysd/actionlint:latest + + unit-tests: + name: Unit tests + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + - run: pip install -r requirements-dev.txt + - name: Pytest (unit) with coverage + run: >- + pytest -m "not integration" --cov=bot --cov-report=term-missing + --cov-report=xml -q + - name: Coverage summary + if: always() + run: | + echo "### Coverage" >> "$GITHUB_STEP_SUMMARY" + python -m coverage report --format=total | + xargs -I{} echo "Total coverage: **{}%**" >> "$GITHUB_STEP_SUMMARY" + - uses: actions/upload-artifact@v4 + with: + name: coverage-xml + path: coverage.xml + + integration-tests: + name: Integration tests + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + - run: pip install -r requirements-dev.txt + - name: Pytest (integration, local HTTP servers only — no internet needed) + run: pytest -m integration -q + + security: + name: Dependency audit & SBOM + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + - run: pip install -r requirements-dev.txt + - name: pip-audit (known CVEs) + run: pip-audit -r requirements.txt --strict + - name: Generate CycloneDX SBOM + run: cyclonedx-py requirements requirements.txt --output-format json --outfile sbom.json + - uses: actions/upload-artifact@v4 + with: + name: sbom-cyclonedx + path: sbom.json + + docker-build: + name: Docker build (PR validation) + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v7 + - uses: docker/setup-buildx-action@v4 + - uses: docker/build-push-action@v7 + with: + context: . + push: false + tags: telegram-rss-bot:ci + cache-from: type=gha + cache-to: type=gha,mode=max + + manifest-validation: + name: Kubernetes manifests (kubeconform) + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v7 + - uses: helm/kubeconform-action@v1.6.0 + with: + strict: true + summary: true + manifests: kubernetes/kube-manifest.yaml diff --git a/.github/workflows/pr-size.yml b/.github/workflows/pr-size.yml new file mode 100644 index 0000000..cfb7acf --- /dev/null +++ b/.github/workflows/pr-size.yml @@ -0,0 +1,45 @@ +name: PR size + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + pull-requests: write + statuses: write + +jobs: + size: + name: Label & badge + runs-on: ubuntu-latest + steps: + - name: Compute size, add label and status badge + env: + GH_TOKEN: ${{ github.token }} + PR: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha }} + run: | + stats=$(gh api "repos/$REPO/pulls/$PR" --jq '[.additions,.deletions]|@csv') + add=$(echo "$stats" | cut -d, -f1) + del=$(echo "$stats" | cut -d, -f2) + total=$((add + del)) + if [ $total -lt 50 ]; then size="size/xs" + elif [ $total -lt 250 ]; then size="size/s" + elif [ $total -lt 1000 ]; then size="size/m" + elif [ $total -lt 2500 ]; then size="size/l" + else size="size/xl"; fi + + # create labels on first use (ignore conflicts) + gh api -X POST "repos/$REPO/labels" -f name="$size" -f color=ededed || true + gh pr edit "$PR" --repo "$REPO" --remove-label \ + size/xs,size/s,size/m,size/l,size/xl 2>/dev/null || true + gh pr edit "$PR" --repo "$REPO" --add-label "$size" + + desc="$total changed lines (+$add/-$del)" + state=success + [ $total -ge 2500 ] && state=warning + gh api -X POST "repos/$REPO/statuses/$SHA" \ + -f state="$state" -f context="PR size" \ + -f description="$desc" -f target_url="https://github.com/$REPO/pull/$PR" + echo ":ruler: PR size: **$size** ($desc)" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml deleted file mode 100644 index 847e796..0000000 --- a/.github/workflows/python-app.yml +++ /dev/null @@ -1,58 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: Python application CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - workflow_dispatch: - -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - - name: Set up Python 3.10 - uses: actions/setup-python@v7 - with: - python-version: 3.x - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - - name: Set up Docker - uses: docker/setup-buildx-action@v4 - - - name: Log into private Docker registry exo.container-registry.com - uses: docker/login-action@v4 - with: - registry: exo.container-registry.com/pittet-sebastien/telegram-rss-bot - username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }} - password: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - - - name: Build Docker image and push to exo.container-registry.com - uses: docker/build-push-action@v7 - with: - context: . - push: true - tags: exo.container-registry.com/pittet-sebastien/telegram-rss-bot:latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7cd88ab --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +name: Release image + +on: + push: + branches: [master] + tags: ["v*.*.*"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +env: + REGISTRY: exo.container-registry.com + IMAGE: exo.container-registry.com/pittet-sebastien/telegram-rss-bot + +jobs: + build-push: + name: Build & push container image + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }} + password: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + + - name: Docker metadata (tags & labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=sha,format=short + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push (amd64 + arm64) + uses: docker/build-push-action@v7 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + provenance: true + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Generate and push SBOM + run: | + pip install --quiet cyclonedx-bom==7.5.0 + cyclonedx-py requirements requirements.txt --output-format json --outfile sbom.json + echo "SBOM attached to workflow artifacts (image tags: ${{ steps.meta.outputs.tags }})" + + - uses: actions/upload-artifact@v4 + with: + name: release-sbom + path: sbom.json diff --git a/README.md b/README.md index 3a6792b..32872cc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # Telegram-RSS-bot + +[![CI](https://github.com/SebastienPittet/Telegram-RSS-Bot/actions/workflows/ci.yml/badge.svg)](https://github.com/SebastienPittet/Telegram-RSS-Bot/actions/workflows/ci.yml) +[![Release image](https://github.com/SebastienPittet/Telegram-RSS-Bot/actions/workflows/release.yml/badge.svg)](https://github.com/SebastienPittet/Telegram-RSS-Bot/actions/workflows/release.yml) +![PR size](https://img.shields.io/badge/PR%20size-labeled%20automatically-informational) +![Python](https://img.shields.io/badge/python-3.12-blue) Bot for telegram channel, example with: [Swiss-IT-News](https://t.me/SwissITNews) # How to use this bot