Implement automatic versioning and display in the application
- Added a step in the CI/CD pipeline to generate a version based on the branch and commit history. - Updated Dockerfile to accept the application version as an argument. - Modified the configuration to retrieve the application version from the environment variable. - Created a new API endpoint to expose the application version. - Added a new Svelte component to display the application version in the UI.
This commit is contained in:
@@ -13,6 +13,60 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout código
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Necesario para obtener el historial completo de git
|
||||
|
||||
# ------------------------
|
||||
# Generar versión automática
|
||||
# ------------------------
|
||||
- name: Generar versión automática
|
||||
id: version
|
||||
run: |
|
||||
# Obtener año y mes actual
|
||||
YEAR=$(date +%y)
|
||||
MONTH=$(date +%m)
|
||||
|
||||
# Detectar rama actual usando variables de Gitea
|
||||
BRANCH="${GITHUB_REF##*/}"
|
||||
|
||||
if [ "$BRANCH" = "development" ]; then
|
||||
# Para development: YY.MM.1.<short-git-hash>
|
||||
SHORT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
VERSION="${YEAR}.${MONTH}.${{ secrets.VERSION_MAYOR }}.${SHORT_HASH}"
|
||||
elif [ "$BRANCH" = "main" ]; then
|
||||
# Para main: YY.MM.0.<commit-count>
|
||||
# Intentar obtener el último tag
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$LAST_TAG" ]; then
|
||||
# No hay tags, usar commit count total (primera ejecución)
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
echo "⚠️ No se encontraron tags. Usando commit count total: ${COMMIT_COUNT}"
|
||||
else
|
||||
# Hay tags, contar commits desde el último tag
|
||||
COMMIT_COUNT=$(git rev-list ${LAST_TAG}..HEAD --count)
|
||||
# Si es 0, significa que estamos en el mismo commit del tag, incrementar
|
||||
if [ "$COMMIT_COUNT" -eq 0 ]; then
|
||||
COMMIT_COUNT=1
|
||||
fi
|
||||
echo "📌 Último tag: ${LAST_TAG}"
|
||||
echo "🔢 Commits desde el último tag: ${COMMIT_COUNT}"
|
||||
fi
|
||||
|
||||
VERSION="${YEAR}.${MONTH}.${{ secrets.VERSION_MAYOR }}.${COMMIT_COUNT}"
|
||||
else
|
||||
# Fallback para otras ramas
|
||||
SHORT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
VERSION="${YEAR}.${MONTH}.99.${SHORT_HASH}"
|
||||
fi
|
||||
|
||||
# Guardar versión en output para usarla en steps posteriores
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "BRANCH=${BRANCH}" >> $GITHUB_OUTPUT
|
||||
|
||||
# Mostrar en logs
|
||||
echo "📦 Versión generada: ${VERSION}"
|
||||
echo "🌿 Rama: ${BRANCH}"
|
||||
|
||||
- name: Login a Harbor
|
||||
run: |
|
||||
@@ -27,6 +81,7 @@ jobs:
|
||||
- name: Build backend
|
||||
run: |
|
||||
docker build \
|
||||
--build-arg APP_VERSION=${{ steps.version.outputs.VERSION }} \
|
||||
-t dev.aduanasoft.com/anexo76/backend:latest \
|
||||
-f ./backend/Dockerfile \
|
||||
./backend
|
||||
@@ -55,6 +110,19 @@ jobs:
|
||||
run: |
|
||||
docker push dev.aduanasoft.com/anexo76/frontend:latest
|
||||
|
||||
# ------------------------
|
||||
# Crear tag de versión (solo para main)
|
||||
# ------------------------
|
||||
- name: Crear tag de versión
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
VERSION=${{ steps.version.outputs.VERSION }}
|
||||
git config user.name "Gitea Actions"
|
||||
git config user.email "actions@gitea.local"
|
||||
git tag -a "v${VERSION}" -m "Release version ${VERSION}"
|
||||
git push origin "v${VERSION}"
|
||||
echo "✅ Tag v${VERSION} creado y pusheado"
|
||||
|
||||
# ------------------------
|
||||
# Deploy a Development
|
||||
# ------------------------
|
||||
|
||||
@@ -2,6 +2,12 @@ FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ========================================
|
||||
# ARG para recibir la versión desde CI/CD
|
||||
# ========================================
|
||||
ARG APP_VERSION="dev"
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
|
||||
# Instalar dependencias del sistema
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Configuración centralizada de la aplicación usando Pydantic Settings
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
@@ -12,7 +13,9 @@ class Settings(BaseSettings):
|
||||
|
||||
# Application
|
||||
APP_NAME: str = "Anexo76"
|
||||
APP_VERSION: str = "1.0.0"
|
||||
# La versión se obtiene de la variable de entorno APP_VERSION que se pasa desde Docker
|
||||
# Si no existe, usa un valor por defecto de desarrollo
|
||||
APP_VERSION: str = os.getenv("APP_VERSION", "dev-local")
|
||||
DEBUG: bool = True
|
||||
ENVIRONMENT: str = "development"
|
||||
|
||||
|
||||
@@ -77,10 +77,8 @@ async def http_exception_handler(request: Request, exc: HTTPException):
|
||||
|
||||
|
||||
def run_migrations():
|
||||
subprocess.run(
|
||||
["alembic", "upgrade", "head"],
|
||||
check=True
|
||||
)
|
||||
subprocess.run(["alembic", "upgrade", "head"], check=True)
|
||||
|
||||
|
||||
# Inicializar la base de datos
|
||||
@app.on_event("startup")
|
||||
@@ -132,3 +130,24 @@ async def root():
|
||||
async def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy", "environment": settings.ENVIRONMENT}
|
||||
|
||||
|
||||
@app.get("/api/version")
|
||||
async def get_version():
|
||||
"""
|
||||
Endpoint de versión de la aplicación
|
||||
|
||||
Retorna la versión de la aplicación que fue incrustada en la imagen Docker
|
||||
durante el proceso de CI/CD. La versión se genera automáticamente según la rama:
|
||||
- development: YY.MM.1.<short-git-hash>
|
||||
- main: YY.MM.0.<commit-count>
|
||||
|
||||
Returns:
|
||||
dict: Información de versión y entorno
|
||||
"""
|
||||
return {
|
||||
"service": settings.APP_NAME,
|
||||
"version": settings.APP_VERSION,
|
||||
"environment": settings.ENVIRONMENT,
|
||||
"debug": settings.DEBUG,
|
||||
}
|
||||
|
||||
90
frontend/src/lib/components/app-version.svelte
Normal file
90
frontend/src/lib/components/app-version.svelte
Normal file
@@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { api } from '$lib/api';
|
||||
|
||||
/**
|
||||
* Componente para mostrar la versión de la aplicación
|
||||
* Consume el endpoint /api/version del backend para obtener información de versión
|
||||
*/
|
||||
|
||||
interface VersionInfo {
|
||||
service: string;
|
||||
version: string;
|
||||
environment: string;
|
||||
debug: boolean;
|
||||
}
|
||||
|
||||
let versionInfo: VersionInfo | null = $state(null);
|
||||
let loading: boolean = $state(true);
|
||||
let error: string | null = $state(null);
|
||||
|
||||
/**
|
||||
* Obtener información de versión desde el backend
|
||||
*/
|
||||
async function fetchVersion() {
|
||||
try {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
const response = await api.get<VersionInfo>('/version');
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
versionInfo = response.data;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error al obtener versión:', err);
|
||||
error = err instanceof Error ? err.message : 'Error desconocido';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Cargar versión al montar el componente
|
||||
onMount(() => {
|
||||
fetchVersion();
|
||||
});
|
||||
|
||||
/**
|
||||
* Obtener color del badge según el entorno
|
||||
*/
|
||||
function getEnvironmentColor(env: string): string {
|
||||
switch (env?.toLowerCase()) {
|
||||
case 'production':
|
||||
return 'bg-green-600 text-white';
|
||||
case 'development':
|
||||
return 'bg-yellow-600 text-white';
|
||||
case 'staging':
|
||||
return 'bg-blue-600 text-white';
|
||||
default:
|
||||
return 'bg-gray-600 text-white';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Componente de versión -->
|
||||
<div class="flex items-center app-version">
|
||||
{#if loading}
|
||||
<div class="text-xs text-muted-foreground">Cargando versión...</div>
|
||||
{:else if error}
|
||||
<div class="text-xs text-destructive">Error: {error}</div>
|
||||
{:else if versionInfo}
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
|
||||
<!-- Versión -->
|
||||
<span class="font-mono font-semibold text-foreground">
|
||||
v{versionInfo.version}
|
||||
</span>
|
||||
|
||||
<!-- Indicador de debug (solo si está activo) -->
|
||||
{#if versionInfo.debug}
|
||||
<span class="rounded bg-orange-600 px-2 py-0.5 text-xs font-medium text-white">
|
||||
DEBUG
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -17,6 +17,7 @@
|
||||
import { goto } from "$app/navigation";
|
||||
import { browser } from "$app/environment";
|
||||
import { getBackendAssetUrl } from "$lib/utils";
|
||||
import AppVersion from "$lib/components/app-version.svelte";
|
||||
|
||||
let { user }: { user: { name: string; email: string; avatar: string } } = $props();
|
||||
const sidebar = useSidebar();
|
||||
@@ -172,6 +173,10 @@
|
||||
<LogOutIcon />
|
||||
Log out
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<div class="px-2 py-2">
|
||||
<AppVersion />
|
||||
</div>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
|
||||
Reference in New Issue
Block a user