Files
sientia-dataops-model-manager/.github/workflows/quality-gate.yml
Bruno Domingues b102f79087 SIENTIAPDE-1243: Remove OPC server integration and add code quality tools.
This commit removes the OPC server integration from the Model Manager, including related activities, repositories, metrics, and configuration. It also adds code quality tools such as Ruff (linting/formatting), mypy (type checking), and Bandit (security analysis) along with a validation script and CI/CD integration for automated code validation. The README has been updated to reflect these changes.
2025-10-01 16:25:08 -03:00

234 lines
9.4 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 = '0.0.0';
if (releases.length > 0) {
lastReleaseVersion = releases[0].tag_name.replace(/^v/, '');
console.log(`Latest release version: ${lastReleaseVersion}`);
} else {
console.log('No releases found, using 0.0.0 as baseline');
}
// Calcular a nova versão baseada no branch
const newVersion = calculateVersion(lastReleaseVersion, branchName);
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: 🔧 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 -r ${{ steps.prepare-requirements.outputs.PROCESSED_REQUIREMENTS_FILE }}
pip install -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' }}