Files
sientia-dataops-model-manager/.github/workflows/quality-gate.yml
Bruno Domingues 4285ddb59d SIENTIAPDE-1243: Improve versioning logic in quality gate and optimize workflow
This commit refactors the version calculation logic in the quality gate workflow to handle initial releases and improve accuracy. It also adds disk space cleanup to the workflow and uses --no-cache-dir when installing dependencies to prevent caching issues.
2025-10-02 12:18:52 -03:00

250 lines
10 KiB
YAML

name: Quality gate
on:
# push:
# branches:
# - main
pull_request:
branches:
- main
types: [ opened, synchronize, reopened ]
jobs:
sonar:
name: SonarQube Analysis
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: ⬇️ Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Calculate Version
id: calculate-version
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
// Função para calcular nova versão baseada no branch
function calculateVersion(lastVersion, branchName) {
const parseVersion = (v) => {
const match = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-rc(\d+))?$/);
if (!match) throw new Error(`Invalid version format: ${v}`);
return {
major: parseInt(match[1]),
minor: parseInt(match[2]),
patch: parseInt(match[3]),
rc: match[4] ? parseInt(match[4]) : null
};
};
const current = parseVersion(lastVersion);
if (branchName.startsWith('release/')) {
return `${current.major + 1}.0.0`;
} else if (branchName.startsWith('feature/')) {
return `${current.major}.${current.minor + 1}.0`;
} else if (branchName.startsWith('fix/')) {
return `${current.major}.${current.minor}.${current.patch + 1}`;
} else if (branchName.startsWith('rc/')) {
if (current.rc !== null) {
return `${current.major}.${current.minor}.${current.patch}-rc${current.rc + 1}`;
} else {
return `${current.major}.${current.minor}.${current.patch}-rc1`;
}
}
return null; // Não sugerir para outros tipos de branch
}
try {
const branchName = context.payload.pull_request.head.ref;
console.log(`Branch name: ${branchName}`);
// Validar se o branch segue os padrões aceitos
const validPrefixes = ['release/', 'feature/', 'fix/', 'rc/'];
const isValidBranch = validPrefixes.some(prefix => branchName.startsWith(prefix));
if (!isValidBranch) {
const errorMessage = `## 🚨 Erro: Nome do Branch Inválido\n\n` +
`O branch \`${branchName}\` não segue os padrões de nomenclatura aceitos.\n\n` +
`### 📝 Padrões Aceitos:\n` +
`- \`release/*\`: Para releases de major version (ex: release/v2.0.0)\n` +
`- \`feature/*\`: Para novas funcionalidades (ex: feature/nova-funcionalidade)\n` +
`- \`fix/*\`: Para correções de bugs (ex: fix/correcao-bug)\n` +
`- \`rc/*\`: Para release candidates (ex: rc/v1.2.0-rc1)\n\n` +
`### 🔧 Como corrigir:\n` +
`1. Renomeie o branch para seguir um dos padrões acima\n` +
`2. Ou crie um novo branch com o nome correto\n`;
const prNumber = context.issue.number;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: errorMessage
});
core.setFailed(`Invalid branch name: ${branchName}. Must start with release/, feature/, fix/, or rc/`);
return;
}
// Obter a última tag de release
console.log('Fetching latest release tag...');
const { data: releases } = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 1
});
let lastReleaseVersion = null;
let newVersion = null;
if (releases.length > 0) {
lastReleaseVersion = releases[0].tag_name.replace(/^v/, '');
console.log(`Latest release version: ${lastReleaseVersion}`);
// Calcular a nova versão baseada na última release
newVersion = calculateVersion(lastReleaseVersion, branchName);
} else {
console.log('No releases found, starting from 0.0.0');
lastReleaseVersion = 'N/A';
// Primeira release: começar com 0.0.0 independente do tipo de branch
newVersion = '0.0.0';
}
console.log(`Calculated version: ${newVersion}`);
// Exportar a versão como output
core.setOutput('version', newVersion);
// Adicionar comentário informativo no PR
const prNumber = context.issue.number;
const infoMessage = `## ✅ Versão Calculada Automaticamente\n\n` +
`**Branch:** \`${branchName}\`\n` +
`**Última release:** \`${lastReleaseVersion}\`\n` +
`**Nova versão:** \`${newVersion}\`\n\n` +
`### 📝 Regras Aplicadas:\n` +
`- \`release/*\`: Aumenta major, zera minor e patch (ex: 2.0.0)\n` +
`- \`feature/*\`: Mantém major, aumenta minor, zera patch (ex: 1.2.0)\n` +
`- \`fix/*\`: Mantém major e minor, aumenta patch (ex: 1.1.3)\n` +
`- \`rc/*\`: Mantém versão base, aumenta RC (ex: 1.1.2-rc2)\n`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: infoMessage
});
} catch (error) {
console.error('Error during version calculation:', error);
const prNumber = context.issue.number;
const errorMessage = `## 🚨 Erro no Cálculo de Versão\n\n` +
`Ocorreu um erro durante o cálculo da versão:\n\n\`\`\`\n${error.message}\n\`\`\`\n\n` +
`Por favor, verifique se o nome do branch está correto e tente novamente.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: errorMessage
});
core.setFailed(`Version calculation error: ${error.message}`);
}
- name: Generate App Token
id: generate-app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: 'Aignosi'
repositories: 'sientia-dataops-library,sientia-mlops-library'
- name: Prepare requirements.txt
id: prepare-requirements
run: |
sed -e "s|git+ssh://git@github.com/|git+https://github.com/|g" \
-e "s|git@github.com:|git+https://github.com/|g" \
requirements.txt > requirements_prepared.txt
echo "PROCESSED_REQUIREMENTS_FILE=requirements_prepared.txt" >> $GITHUB_OUTPUT
- name: Configure Git to use App Token
env:
GH_APP_TOKEN: ${{ steps.generate-app-token.outputs.token }}
run: |
git config --global url."https://oauth2:${GH_APP_TOKEN}@github.com/".insteadOf "https://github.com/"
- name: 🧹 Free Disk Space
run: |
echo "Disk space before cleanup:"
df -h
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/local/lib/android
sudo rm -rf /opt/ghc
sudo rm -rf /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
echo "Disk space after cleanup:"
df -h
- name: 🔧 Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: 🗄️ Cache Python dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles(steps.prepare-requirements.outputs.PROCESSED_REQUIREMENTS_FILE) }}
restore-keys: |
${{ runner.os }}-pip-
- name: 📦 Install Dependencies
run: |
python -m pip install --upgrade pip
pip install --no-cache-dir -r ${{ steps.prepare-requirements.outputs.PROCESSED_REQUIREMENTS_FILE }}
pip install --no-cache-dir -r requirements-dev.txt
- name: 📝 Code Formatting Check (Ruff)
run: |
echo "Checking code formatting..."
ruff format --check model_manager/ tests/
continue-on-error: false
- name: 🔎 Code Linting (Ruff)
run: |
echo "Running linting checks..."
ruff check model_manager/ tests/
continue-on-error: false
- name: 🏷️ Type Checking (mypy)
run: |
echo "Running type checks..."
mypy model_manager/
continue-on-error: true
- name: 🔒 Security Analysis (Bandit)
run: |
echo "Running security analysis..."
bandit -r model_manager/ -ll -q
continue-on-error: true
- name: 🧪 Run Tests with Pytest
run: |
pytest tests --junitxml=pytest.xml --cov=model_manager --cov-report=xml --cov-report=term
- name: Run SonarQube Analysis
uses: SonarSource/sonarqube-scan-action@v5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
with:
args: >
-Dsonar.projectVersion=${{ steps.calculate-version.outputs.version || '0.0.0' }}