diff --git a/.gitea/workflows/python-auto-release.yml b/.gitea/workflows/python-auto-release.yml new file mode 100644 index 0000000..ed9fd1e --- /dev/null +++ b/.gitea/workflows/python-auto-release.yml @@ -0,0 +1,69 @@ +name: Python Auto Release + +on: + workflow_call: + inputs: + working-directory: + description: 'Directory di lavoro se il progetto non è in root' + type: string + default: '.' + +jobs: + python-auto-release: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Checkout + uses: https://gitea.com/actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate changelog + id: changelog + run: | + CURRENT_TAG=${GITHUB_REF#refs/tags/} + PREV_TAG=$(git tag --sort=-version:refname | sed -n '2p') + + if [ -z "$PREV_TAG" ]; then + CHANGELOG=$(git log --pretty=format:"- %s (%h)" "$CURRENT_TAG") + else + CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREV_TAG}..${CURRENT_TAG}") + fi + + echo "$CHANGELOG" > /tmp/changelog.txt + echo "current_tag=$CURRENT_TAG" >> $GITHUB_OUTPUT + + - name: Read app name from pyproject.toml + id: app-info + run: | + APP_NAME=$(python3 -c " + import re, pathlib + content = pathlib.Path('pyproject.toml').read_text() + m = re.search(r'^name\s*=\s*[\"\'](.*?)[\"\']', content, re.MULTILINE) + print(m.group(1) if m else 'app') + " 2>/dev/null || echo 'app') + echo "name=$APP_NAME" >> $GITHUB_OUTPUT + + - name: Create release + env: + GITEA_TOKEN: ${{ gitea.token }} + SERVER_URL: ${{ gitea.server_url }} + REPOSITORY: ${{ gitea.repository }} + run: | + CURRENT_TAG=${{ steps.changelog.outputs.current_tag }} + APP_NAME=${{ steps.app-info.outputs.name }} + CHANGELOG=$(cat /tmp/changelog.txt) + + curl -s -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: token $GITEA_TOKEN" \ + "$SERVER_URL/api/v1/repos/$REPOSITORY/releases" \ + -d "{ + \"tag_name\": \"$CURRENT_TAG\", + \"name\": \"$APP_NAME $CURRENT_TAG\", + \"body\": $(echo "$CHANGELOG" | jq -Rs .), + \"draft\": false, + \"prerelease\": false + }" diff --git a/.gitea/workflows/python-dependency-check.yml b/.gitea/workflows/python-dependency-check.yml new file mode 100644 index 0000000..5ce2027 --- /dev/null +++ b/.gitea/workflows/python-dependency-check.yml @@ -0,0 +1,101 @@ +name: Python Dependency Audit + +on: + workflow_call: + inputs: + python-version: + type: string + default: '3.11' + install-extras: + type: string + default: 'dev' + working-directory: + type: string + default: '.' + workflow_dispatch: + +jobs: + python-dependency-audit: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Checkout + uses: https://gitea.com/actions/checkout@v4 + + - name: Setup Python + uses: https://github.com/actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version || '3.11' }} + + - name: Cache pip + uses: https://gitea.com/actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ inputs.python-version }}-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ inputs.python-version }}- + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip pip-audit + if [ -n "${{ inputs.install-extras }}" ]; then + pip install -e ".[${{ inputs.install-extras }}]" + elif [ -f requirements.txt ]; then + pip install -r requirements.txt + else + pip install -e "." + fi + + - name: Security audit (pip-audit) + id: audit + run: | + pip-audit --format=json --output=audit.json 2>/dev/null || true + VULNS=$(python3 -c " + import json, sys + try: + data = json.load(open('audit.json')) + deps = data.get('dependencies', []) + count = sum(len(d.get('vulns', [])) for d in deps) + print(count) + except Exception: + print(0) + ") + echo "vulnerabilities=$VULNS" >> $GITHUB_OUTPUT + + - name: Open issue if vulnerabilities found + if: steps.audit.outputs.vulnerabilities != '0' + env: + GITEA_TOKEN: ${{ gitea.token }} + SERVER_URL: ${{ gitea.server_url }} + REPOSITORY: ${{ gitea.repository }} + run: | + VULNS="${{ steps.audit.outputs.vulnerabilities }}" + DATE=$(date '+%Y-%m-%d') + + VULN_LIST=$(python3 -c " + import json + try: + data = json.load(open('audit.json')) + lines = [] + for dep in data.get('dependencies', []): + for v in dep.get('vulns', []): + lines.append(f\"- {dep['name']} {dep.get('version','?')}: {v.get('id','?')} ({v.get('description','')[:80]})\") + print('\n'.join(lines[:20])) + except Exception: + print('N/A') + ") + + printf '## Security Audit Python — %s\n\n### Vulnerabilità trovate: %s\n\n```\n%s\n```\n' \ + "$DATE" "$VULNS" "$VULN_LIST" > /tmp/body.md + + curl -s -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: token $GITEA_TOKEN" \ + "$SERVER_URL/api/v1/repos/$REPOSITORY/issues" \ + -d "{ + \"title\": \"[$DATE] Security Python: $VULNS vulnerabilità rilevate\", + \"body\": $(jq -Rs . /tmp/body.md) + }" diff --git a/.gitea/workflows/python-dependency-outdated.yml b/.gitea/workflows/python-dependency-outdated.yml new file mode 100644 index 0000000..5cb0a17 --- /dev/null +++ b/.gitea/workflows/python-dependency-outdated.yml @@ -0,0 +1,89 @@ +name: Python Dependency Outdated + +on: + workflow_call: + inputs: + python-version: + type: string + default: '3.11' + install-extras: + type: string + default: 'dev' + working-directory: + type: string + default: '.' + workflow_dispatch: + +jobs: + python-dependency-outdated: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Checkout + uses: https://gitea.com/actions/checkout@v4 + + - name: Setup Python + uses: https://github.com/actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version || '3.11' }} + + - name: Cache pip + uses: https://gitea.com/actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ inputs.python-version }}-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ inputs.python-version }}- + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -n "${{ inputs.install-extras }}" ]; then + pip install -e ".[${{ inputs.install-extras }}]" + elif [ -f requirements.txt ]; then + pip install -r requirements.txt + else + pip install -e "." + fi + + - name: Check outdated packages + id: outdated + run: | + pip list --outdated --format=json > outdated.json 2>/dev/null || echo '[]' > outdated.json + COUNT=$(python3 -c "import json; print(len(json.load(open('outdated.json'))))") + echo "count=$COUNT" >> $GITHUB_OUTPUT + + - name: Open issue if packages outdated + if: steps.outdated.outputs.count != '0' + env: + GITEA_TOKEN: ${{ gitea.token }} + SERVER_URL: ${{ gitea.server_url }} + REPOSITORY: ${{ gitea.repository }} + run: | + COUNT="${{ steps.outdated.outputs.count }}" + DATE=$(date '+%Y-%m-%d') + + OUTDATED_LIST=$(python3 -c " + import json + try: + data = json.load(open('outdated.json')) + lines = [f\"- {p['name']}: {p['version']} → {p['latest_version']}\" for p in data[:20]] + print('\n'.join(lines)) + except Exception: + print('N/A') + ") + + printf '## Dipendenze Python Obsolete — %s\n\n### Pacchetti da aggiornare: %s\n\n```\n%s\n```\n' \ + "$DATE" "$COUNT" "$OUTDATED_LIST" > /tmp/body.md + + curl -s -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: token $GITEA_TOKEN" \ + "$SERVER_URL/api/v1/repos/$REPOSITORY/issues" \ + -d "{ + \"title\": \"[$DATE] Dipendenze Python: $COUNT pacchetti obsoleti\", + \"body\": $(jq -Rs . /tmp/body.md) + }" diff --git a/.gitea/workflows/python-docker-release.yml b/.gitea/workflows/python-docker-release.yml new file mode 100644 index 0000000..9abad31 --- /dev/null +++ b/.gitea/workflows/python-docker-release.yml @@ -0,0 +1,116 @@ +name: Python Docker Release + +on: + workflow_call: + inputs: + python-version: + type: string + default: '3.11' + install-extras: + description: 'Extras pip da installare (es: dev, test)' + type: string + default: 'dev' + runner: + type: string + default: 'catthehacker-latest' + working-directory: + description: 'Directory di lavoro se il progetto non è in root' + type: string + default: '.' + dockerfile: + description: 'Path al Dockerfile' + type: string + default: './Dockerfile' + +jobs: + python-docker-release: + runs-on: ${{ inputs.runner }} + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Checkout + uses: https://gitea.com/actions/checkout@v4 + + - name: Setup Python + uses: https://github.com/actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version || '3.11' }} + + - name: Cache pip + uses: https://gitea.com/actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ inputs.python-version }}-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ inputs.python-version }}- + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -n "${{ inputs.install-extras }}" ]; then + pip install -e ".[${{ inputs.install-extras }}]" + elif [ -f requirements-dev.txt ]; then + pip install -r requirements-dev.txt + elif [ -f requirements.txt ]; then + pip install -r requirements.txt + else + pip install -e "." + fi + + - name: Lint (ruff) + run: ruff check . + + - name: Format check (ruff) + run: ruff format --check . + + - name: Type check (mypy) + run: mypy . + + - name: Test (pytest) + run: pytest --tb=short -q + + - name: Login to container registry + uses: https://gitea.com/docker/login-action@v3 + with: + registry: gitea.pzetatouch.it + username: ${{ secrets.G_USER }} + password: ${{ secrets.NPM_TOKEN }} + + - name: Determine image tags + id: tags + run: | + APP_NAME=$(python -c " + import tomllib, pathlib + data = tomllib.loads(pathlib.Path('pyproject.toml').read_text()) + print(data['project']['name']) + " 2>/dev/null || python3 -c " + import re, pathlib + content = pathlib.Path('pyproject.toml').read_text() + m = re.search(r'^name\s*=\s*[\"\'](.*?)[\"\']', content, re.MULTILINE) + print(m.group(1) if m else 'unknown') + ") + VERSION=$(python -c " + import tomllib, pathlib + data = tomllib.loads(pathlib.Path('pyproject.toml').read_text()) + print(data['project']['version']) + " 2>/dev/null || python3 -c " + import re, pathlib + content = pathlib.Path('pyproject.toml').read_text() + m = re.search(r'^version\s*=\s*[\"\'](.*?)[\"\']', content, re.MULTILINE) + print(m.group(1) if m else '0.0.0') + ") + REGISTRY="gitea.pzetatouch.it/pzeta_touch" + echo "version_tag=${REGISTRY}/${APP_NAME}:${VERSION}" >> $GITHUB_OUTPUT + echo "latest_tag=${REGISTRY}/${APP_NAME}:latest" >> $GITHUB_OUTPUT + + - name: Build and push Docker image + uses: https://gitea.com/docker/build-push-action@v6 + with: + push: true + context: ${{ inputs.working-directory }} + file: ${{ inputs.dockerfile }} + tags: | + ${{ steps.tags.outputs.version_tag }} + ${{ steps.tags.outputs.latest_tag }} diff --git a/.gitea/workflows/python-quality-gates.yml b/.gitea/workflows/python-quality-gates.yml new file mode 100644 index 0000000..3bf38d7 --- /dev/null +++ b/.gitea/workflows/python-quality-gates.yml @@ -0,0 +1,65 @@ +name: Python Quality Gates + +on: + workflow_call: + inputs: + python-version: + type: string + default: '3.11' + install-extras: + description: 'Extras pip da installare (es: dev, test). Lasciare vuoto per solo requirements.txt' + type: string + default: 'dev' + working-directory: + description: 'Directory di lavoro se il progetto non è in root' + type: string + default: '.' + +jobs: + python-quality-gates: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Checkout + uses: https://gitea.com/actions/checkout@v4 + + - name: Setup Python + uses: https://github.com/actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version || '3.11' }} + + - name: Cache pip + uses: https://gitea.com/actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ inputs.python-version }}-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ inputs.python-version }}- + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -n "${{ inputs.install-extras }}" ]; then + pip install -e ".[${{ inputs.install-extras }}]" + elif [ -f requirements-dev.txt ]; then + pip install -r requirements-dev.txt + elif [ -f requirements.txt ]; then + pip install -r requirements.txt + else + pip install -e "." + fi + + - name: Lint (ruff) + run: ruff check . + + - name: Format check (ruff) + run: ruff format --check . + + - name: Type check (mypy) + run: mypy . + + - name: Test (pytest) + run: pytest --tb=short -q diff --git a/examples/python/ci.yml b/examples/python/ci.yml new file mode 100644 index 0000000..191d08a --- /dev/null +++ b/examples/python/ci.yml @@ -0,0 +1,31 @@ +# Copia in: .gitea/workflows/ci.yml +# Usato da: microservizi Python con FastAPI / pyproject.toml +# +# Requisiti pyproject.toml: +# [project.optional-dependencies] +# dev = ["ruff", "mypy", "pytest", ...] +# +# Input opzionali: +# python-version: '3.11' # default già impostato +# install-extras: 'dev' # default già impostato + +name: CI + +on: + push: + branches: + - main + paths-ignore: + - '**.md' + - 'docs/**' + pull_request: + branches: + - main + +jobs: + quality-gates: + uses: https://gitea.com/Punga78/shared-actions/.gitea/workflows/python-quality-gates.yml@v1 + secrets: inherit + # with: + # python-version: '3.11' # opzionale, default già impostato + # install-extras: 'dev' # opzionale, default già impostato diff --git a/examples/python/maintenance.yml b/examples/python/maintenance.yml new file mode 100644 index 0000000..427b787 --- /dev/null +++ b/examples/python/maintenance.yml @@ -0,0 +1,42 @@ +# Copia in: .gitea/workflows/maintenance.yml +# Trigger: +# - PR merged → cleanup branch sorgente +# - Ogni lunedì 08:00 → security audit vulnerabilità (settimanale) +# - Ogni 1° del mese → controllo pacchetti obsoleti (mensile) +# - Manuale → workflow_dispatch (esegue tutti i job di manutenzione) + +name: Maintenance + +on: + pull_request: + types: [closed] + schedule: + - cron: '0 8 * * 1' # ogni lunedì alle 08:00 → security audit + - cron: '0 8 1 * *' # ogni 1° del mese alle 08:00 → outdated check + workflow_dispatch: + +jobs: + branch-cleanup: + if: gitea.event_name == 'pull_request' && gitea.event.pull_request.merged == true + uses: https://gitea.com/Punga78/shared-actions/.gitea/workflows/branch-cleanup.yml@v1 + secrets: inherit + + dependency-audit: + if: > + gitea.event_name == 'workflow_dispatch' || + (gitea.event_name == 'schedule' && gitea.event.schedule == '0 8 * * 1') + uses: https://gitea.com/Punga78/shared-actions/.gitea/workflows/python-dependency-check.yml@v1 + secrets: inherit + # with: + # python-version: '3.11' + # install-extras: 'dev' + + dependency-outdated: + if: > + gitea.event_name == 'workflow_dispatch' || + (gitea.event_name == 'schedule' && gitea.event.schedule == '0 8 1 * *') + uses: https://gitea.com/Punga78/shared-actions/.gitea/workflows/python-dependency-outdated.yml@v1 + secrets: inherit + # with: + # python-version: '3.11' + # install-extras: 'dev' diff --git a/examples/python/release.yml b/examples/python/release.yml new file mode 100644 index 0000000..0e978bb --- /dev/null +++ b/examples/python/release.yml @@ -0,0 +1,30 @@ +# Copia in: .gitea/workflows/release.yml +# Trigger: git tag v* → build Docker + crea release Gitea +# +# NOTA: i job girano in parallelo (non sequenziali con needs). +# Gitea bug #31900: needs + workflow_call + checkout causa errori di autenticazione. +# +# Prerequisiti: +# - Dockerfile presente nella root del progetto +# - pyproject.toml con [project] name e version +# - Secrets: G_USER (docker login), NPM_TOKEN (usato come password registry) + +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + docker-release: + uses: https://gitea.com/Punga78/shared-actions/.gitea/workflows/python-docker-release.yml@v1 + secrets: inherit + with: + runner: catthehacker-latest # runner con Docker pre-installato + # python-version: '3.11' # opzionale + # install-extras: 'dev' # opzionale + + auto-release: + uses: https://gitea.com/Punga78/shared-actions/.gitea/workflows/python-auto-release.yml@v1 + secrets: inherit