docs: READMEs completos, estructura final de arquetipos
Mueve los reusable workflows a .gitea/workflows/ (restriccion real de Gitea: workflow_call no puede vivir en una ruta arbitraria, confirmado en vivo). Corrige todos los 'uses:' cross-repo a URL absoluta (https://gitea.cruzcloud.net/...) -- la forma corta de GitHub se resuelve contra github.com en esta instancia, confirmado en vivo con un test real (reusable workflow + composite action ejecutando gitleaks de punta a punta). Limpia los archivos de prueba.
This commit is contained in:
1 parent
018d424a1c
commit
a3c022c024
11 files changed
+514
-49
No files matched your search
@@ -1,12 +0,0 @@
|
||||
name: TEST - verificacion de reusable workflow + composite action
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
call-reusable:
|
||||
uses: https://gitea.cruzcloud.net/devops/platform-templates/.gitea/workflows/_test-reusable.yaml@main
|
||||
with:
|
||||
greeting: "hola desde el caller"
|
||||
@@ -1,17 +0,0 @@
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
greeting:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/gitleaks-step@main
|
||||
with:
|
||||
source: devsecops-snippets
|
||||
- shell: bash
|
||||
run: echo "Reusable workflow input recibido -> '${{ inputs.greeting }}'"
|
||||
@@ -0,0 +1,193 @@
|
||||
# Arquetipo de pipeline: Next.js standalone.
|
||||
# Extraído del pipeline real de ecommerce-frontend (apps-registry),
|
||||
# generalizando lo específico de esa app (rutas hardcodeadas, checks
|
||||
# de negocio del catálogo) en inputs.
|
||||
#
|
||||
# Cómo usarlo desde tu repo: ver pipelines/archetype-nextjs-frontend/README.md.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app_dir:
|
||||
description: "Directorio de la app dentro de tu repo (ej. app/)."
|
||||
type: string
|
||||
required: true
|
||||
image_name:
|
||||
description: "Nombre completo de la imagen sin tag (ej. gitea.cruzcloud.net/devops/mi-app)."
|
||||
type: string
|
||||
required: true
|
||||
manifest_file:
|
||||
description: "Manifiesto K8s cuyo tag de imagen se actualiza tras el push (CD)."
|
||||
type: string
|
||||
required: true
|
||||
semgrep_configs:
|
||||
description: "Rulesets de Semgrep, separados por coma."
|
||||
type: string
|
||||
required: false
|
||||
default: "p/typescript,p/react,p/nextjs,p/security-audit"
|
||||
trivy_ignorefile:
|
||||
description: "Ruta a un .trivyignore, si tu app tiene excepciones documentadas."
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
secrets:
|
||||
REGISTRY_USER:
|
||||
required: true
|
||||
REGISTRY_PASSWORD:
|
||||
required: true
|
||||
COSIGN_PRIVATE_KEY:
|
||||
required: true
|
||||
COSIGN_PASSWORD:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
name: Escaneo de Secretos (Gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/gitleaks-step@main
|
||||
with:
|
||||
source: ${{ inputs.app_dir }}
|
||||
|
||||
build:
|
||||
name: Construir y Subir Imagen
|
||||
needs: gitleaks
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
env:
|
||||
APP_DIR: ${{ inputs.app_dir }}
|
||||
MANIFEST_FILE: ${{ inputs.manifest_file }}
|
||||
IMAGE_NAME: ${{ inputs.image_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout del código
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: true
|
||||
|
||||
- name: Definir versión semántica
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "VERSION=v1.0.${{ github.run_number }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validar secretos del Registry
|
||||
shell: bash
|
||||
env:
|
||||
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "${REGISTRY_USER}" || { echo "ERROR: REGISTRY_USER no está configurado."; exit 1; }
|
||||
test -n "${REGISTRY_PASSWORD}" || { echo "ERROR: REGISTRY_PASSWORD no está configurado."; exit 1; }
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/trivy-iac-step@main
|
||||
with:
|
||||
target: ${{ env.MANIFEST_FILE }}
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/semgrep-step@main
|
||||
with:
|
||||
target: ${{ env.APP_DIR }}
|
||||
configs: ${{ inputs.semgrep_configs }}
|
||||
|
||||
- name: Login en Gitea Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.cruzcloud.net
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
logout: true
|
||||
|
||||
# push: false — la imagen se queda cargada localmente para poder
|
||||
# escanearla con Trivy antes de subirla.
|
||||
- name: Construir Imagen
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: ${{ env.APP_DIR }}
|
||||
push: false
|
||||
load: true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/trivy-image-step@main
|
||||
with:
|
||||
image_ref: ${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
ignorefile: ${{ inputs.trivy_ignorefile }}
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/syft-sbom-step@main
|
||||
with:
|
||||
image_ref: ${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
artifact_suffix: ${{ steps.vars.outputs.VERSION }}
|
||||
|
||||
- name: Subir Imagen al Registry
|
||||
id: push
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PUSH_OUTPUT="$(docker push "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}")"
|
||||
echo "${PUSH_OUTPUT}"
|
||||
docker push "${IMAGE_NAME}:latest"
|
||||
DIGEST="$(echo "${PUSH_OUTPUT}" | grep -oE 'sha256:[a-f0-9]{64}' | tail -1)"
|
||||
test -n "${DIGEST}" || { echo "ERROR: no se pudo extraer el digest del push."; exit 1; }
|
||||
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/cosign-sign-verify-step@main
|
||||
with:
|
||||
image_name: ${{ env.IMAGE_NAME }}
|
||||
digest: ${{ steps.push.outputs.digest }}
|
||||
public_key_path: ${{ env.APP_DIR }}/cosign.pub
|
||||
cosign_private_key: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
cosign_password: ${{ secrets.COSIGN_PASSWORD }}
|
||||
|
||||
- name: Verificar promoción segura
|
||||
id: promotion
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin main
|
||||
CURRENT_SHA="${{ github.sha }}"
|
||||
REMOTE_SHA="$(git rev-parse origin/main)"
|
||||
if [ "${CURRENT_SHA}" = "${REMOTE_SHA}" ]; then
|
||||
echo "promote=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "promote=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Hay un commit más reciente; no se actualizará el manifiesto."
|
||||
fi
|
||||
|
||||
- name: Actualizar Manifiesto GitOps (CD)
|
||||
if: steps.promotion.outputs.promote == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ steps.vars.outputs.VERSION }}"
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "[email protected]"
|
||||
git fetch origin main
|
||||
git checkout -B main origin/main
|
||||
sed -i -E "s|(image: ${IMAGE_NAME}:).*|\1${VERSION}|g" "${MANIFEST_FILE}"
|
||||
git add "${MANIFEST_FILE}"
|
||||
if git diff --cached --quiet; then
|
||||
echo "No hay cambios en el manifiesto."
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore(gitops): deploy ${VERSION} [skip ci]"
|
||||
git push origin HEAD:main
|
||||
|
||||
- name: Resumen del pipeline
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Versión: ${{ steps.vars.outputs.VERSION }}"
|
||||
echo "Commit: ${{ github.sha }}"
|
||||
echo "Promoción GitOps: ${{ steps.promotion.outputs.promote }}"
|
||||
echo "========================================"
|
||||
@@ -0,0 +1,193 @@
|
||||
# Arquetipo de pipeline: API Node/Express (o similar, sin SSR).
|
||||
# Extraído del pipeline real de commerce-backend/Medusa (apps-registry).
|
||||
#
|
||||
# Cómo usarlo desde tu repo: ver pipelines/archetype-node-api/README.md.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app_dir:
|
||||
description: "Directorio de la app dentro de tu repo."
|
||||
type: string
|
||||
required: true
|
||||
image_name:
|
||||
description: "Nombre completo de la imagen sin tag."
|
||||
type: string
|
||||
required: true
|
||||
manifest_file:
|
||||
description: "Manifiesto K8s cuyo tag de imagen se actualiza tras el push (CD)."
|
||||
type: string
|
||||
required: true
|
||||
semgrep_configs:
|
||||
description: "Rulesets de Semgrep, separados por coma."
|
||||
type: string
|
||||
required: false
|
||||
default: "p/typescript,p/security-audit,p/owasp-top-ten"
|
||||
trivy_ignorefile:
|
||||
description: "Ruta a un .trivyignore, si tu app tiene excepciones documentadas."
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
secrets:
|
||||
REGISTRY_USER:
|
||||
required: true
|
||||
REGISTRY_PASSWORD:
|
||||
required: true
|
||||
COSIGN_PRIVATE_KEY:
|
||||
required: true
|
||||
COSIGN_PASSWORD:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
name: Escaneo de Secretos (Gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/gitleaks-step@main
|
||||
with:
|
||||
source: ${{ inputs.app_dir }}
|
||||
|
||||
build:
|
||||
name: Construir y Subir Imagen
|
||||
needs: gitleaks
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
env:
|
||||
APP_DIR: ${{ inputs.app_dir }}
|
||||
MANIFEST_FILE: ${{ inputs.manifest_file }}
|
||||
IMAGE_NAME: ${{ inputs.image_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout del código
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: true
|
||||
|
||||
- name: Definir versión
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "VERSION=v1.0.${{ github.run_number }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validar secretos del Registry
|
||||
shell: bash
|
||||
env:
|
||||
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "${REGISTRY_USER}" || { echo "ERROR: REGISTRY_USER no está configurado."; exit 1; }
|
||||
test -n "${REGISTRY_PASSWORD}" || { echo "ERROR: REGISTRY_PASSWORD no está configurado."; exit 1; }
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/trivy-iac-step@main
|
||||
with:
|
||||
target: ${{ env.MANIFEST_FILE }}
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/semgrep-step@main
|
||||
with:
|
||||
target: ${{ env.APP_DIR }}
|
||||
configs: ${{ inputs.semgrep_configs }}
|
||||
|
||||
- name: Login en Gitea Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.cruzcloud.net
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
logout: true
|
||||
|
||||
- name: Construir Imagen
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: ${{ env.APP_DIR }}
|
||||
push: false
|
||||
load: true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
# --timeout 15m0s ya viene por default en trivy-image-step —
|
||||
# commerce-backend (Medusa v2) es exactamente el caso real que
|
||||
# justificó ese default (node_modules completo del framework +
|
||||
# admin-sdk + cli, mucho más que escanear que una API chica).
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/trivy-image-step@main
|
||||
with:
|
||||
image_ref: ${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
ignorefile: ${{ inputs.trivy_ignorefile }}
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/syft-sbom-step@main
|
||||
with:
|
||||
image_ref: ${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
artifact_suffix: ${{ steps.vars.outputs.VERSION }}
|
||||
|
||||
- name: Subir Imagen al Registry
|
||||
id: push
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PUSH_OUTPUT="$(docker push "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}")"
|
||||
echo "${PUSH_OUTPUT}"
|
||||
docker push "${IMAGE_NAME}:latest"
|
||||
DIGEST="$(echo "${PUSH_OUTPUT}" | grep -oE 'sha256:[a-f0-9]{64}' | tail -1)"
|
||||
test -n "${DIGEST}" || { echo "ERROR: no se pudo extraer el digest del push."; exit 1; }
|
||||
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/cosign-sign-verify-step@main
|
||||
with:
|
||||
image_name: ${{ env.IMAGE_NAME }}
|
||||
digest: ${{ steps.push.outputs.digest }}
|
||||
public_key_path: ${{ env.APP_DIR }}/cosign.pub
|
||||
cosign_private_key: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
cosign_password: ${{ secrets.COSIGN_PASSWORD }}
|
||||
|
||||
- name: Verificar promoción segura
|
||||
id: promotion
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin main
|
||||
CURRENT_SHA="${{ github.sha }}"
|
||||
REMOTE_SHA="$(git rev-parse origin/main)"
|
||||
if [ "${CURRENT_SHA}" = "${REMOTE_SHA}" ]; then
|
||||
echo "promote=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "promote=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Hay un commit más reciente; no se actualizará el manifiesto."
|
||||
fi
|
||||
|
||||
- name: Actualizar manifiesto
|
||||
if: steps.promotion.outputs.promote == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ steps.vars.outputs.VERSION }}"
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "[email protected]"
|
||||
git fetch origin main
|
||||
git checkout -B main origin/main
|
||||
sed -i -E "s|(${IMAGE_NAME}:)v[0-9]+\.[0-9]+\.[0-9]+|\1${VERSION}|g" "${MANIFEST_FILE}"
|
||||
git add "${MANIFEST_FILE}"
|
||||
if git diff --cached --quiet; then
|
||||
echo "El manifiesto ya apunta a ${VERSION}."
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore(gitops): deploy ${VERSION} [skip ci]"
|
||||
git push origin HEAD:main
|
||||
|
||||
- name: Resumen del pipeline
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Versión: ${{ steps.vars.outputs.VERSION }}"
|
||||
echo "Commit: ${{ github.sha }}"
|
||||
echo "Promoción GitOps: ${{ steps.promotion.outputs.promote }}"
|
||||
echo "========================================"
|
||||
@@ -0,0 +1,195 @@
|
||||
# Arquetipo de pipeline: sitio estático servido por nginx (MkDocs,
|
||||
# Hugo, Astro, HTML plano). Extraído del pipeline real de docs-portal
|
||||
# (apps-registry).
|
||||
#
|
||||
# Diferencia clave con los otros 2 arquetipos: gitleaks y Trivy IaC
|
||||
# escanean todo APP_DIR (no un único manifest_file) — un sitio estático
|
||||
# suele tener varios manifiestos K8s separados (deployment/service/
|
||||
# ingress) en vez de uno solo.
|
||||
#
|
||||
# Cómo usarlo desde tu repo: ver pipelines/archetype-static-site/README.md.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app_dir:
|
||||
description: "Directorio de la app dentro de tu repo."
|
||||
type: string
|
||||
required: true
|
||||
image_name:
|
||||
description: "Nombre completo de la imagen sin tag."
|
||||
type: string
|
||||
required: true
|
||||
manifest_file:
|
||||
description: "Manifiesto K8s (Deployment) cuyo tag de imagen se actualiza tras el push (CD)."
|
||||
type: string
|
||||
required: true
|
||||
semgrep_configs:
|
||||
description: "Rulesets de Semgrep, separados por coma."
|
||||
type: string
|
||||
required: false
|
||||
default: "p/python,p/security-audit"
|
||||
trivy_ignorefile:
|
||||
description: "Ruta a un .trivyignore, si tu app tiene excepciones documentadas."
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
secrets:
|
||||
REGISTRY_USER:
|
||||
required: true
|
||||
REGISTRY_PASSWORD:
|
||||
required: true
|
||||
COSIGN_PRIVATE_KEY:
|
||||
required: true
|
||||
COSIGN_PASSWORD:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
name: Escaneo de Secretos (Gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/gitleaks-step@main
|
||||
with:
|
||||
source: ${{ inputs.app_dir }}
|
||||
|
||||
build:
|
||||
name: Construir y publicar
|
||||
needs: gitleaks
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
env:
|
||||
APP_DIR: ${{ inputs.app_dir }}
|
||||
MANIFEST_FILE: ${{ inputs.manifest_file }}
|
||||
IMAGE_NAME: ${{ inputs.image_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout del código
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: true
|
||||
|
||||
- name: Definir versión
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "VERSION=v1.0.${{ github.run_number }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validar secretos del Registry
|
||||
shell: bash
|
||||
env:
|
||||
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "${REGISTRY_USER}" || { echo "ERROR: REGISTRY_USER no está configurado."; exit 1; }
|
||||
test -n "${REGISTRY_PASSWORD}" || { echo "ERROR: REGISTRY_PASSWORD no está configurado."; exit 1; }
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/trivy-iac-step@main
|
||||
with:
|
||||
target: ${{ env.APP_DIR }}
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/semgrep-step@main
|
||||
with:
|
||||
target: ${{ env.APP_DIR }}
|
||||
configs: ${{ inputs.semgrep_configs }}
|
||||
|
||||
- name: Login en Gitea Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.cruzcloud.net
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
logout: true
|
||||
|
||||
- name: Construir Imagen
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: ${{ env.APP_DIR }}
|
||||
push: false
|
||||
load: true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
${{ env.IMAGE_NAME }}:latest
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/trivy-image-step@main
|
||||
with:
|
||||
image_ref: ${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
ignorefile: ${{ inputs.trivy_ignorefile }}
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/syft-sbom-step@main
|
||||
with:
|
||||
image_ref: ${{ env.IMAGE_NAME }}:${{ steps.vars.outputs.VERSION }}
|
||||
artifact_suffix: ${{ steps.vars.outputs.VERSION }}
|
||||
|
||||
- name: Subir Imagen al Registry
|
||||
id: push
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PUSH_OUTPUT="$(docker push "${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}")"
|
||||
echo "${PUSH_OUTPUT}"
|
||||
docker push "${IMAGE_NAME}:latest"
|
||||
DIGEST="$(echo "${PUSH_OUTPUT}" | grep -oE 'sha256:[a-f0-9]{64}' | tail -1)"
|
||||
test -n "${DIGEST}" || { echo "ERROR: no se pudo extraer el digest del push."; exit 1; }
|
||||
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: https://gitea.cruzcloud.net/devops/platform-templates/devsecops-snippets/cosign-sign-verify-step@main
|
||||
with:
|
||||
image_name: ${{ env.IMAGE_NAME }}
|
||||
digest: ${{ steps.push.outputs.digest }}
|
||||
public_key_path: ${{ env.APP_DIR }}/cosign.pub
|
||||
cosign_private_key: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
cosign_password: ${{ secrets.COSIGN_PASSWORD }}
|
||||
|
||||
- name: Verificar promoción segura
|
||||
id: promotion
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin main
|
||||
CURRENT_SHA="${{ github.sha }}"
|
||||
REMOTE_SHA="$(git rev-parse origin/main)"
|
||||
if [ "${CURRENT_SHA}" = "${REMOTE_SHA}" ]; then
|
||||
echo "promote=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "promote=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Hay un commit más reciente; no se actualizará el manifiesto."
|
||||
fi
|
||||
|
||||
- name: Actualizar manifiesto GitOps
|
||||
if: steps.promotion.outputs.promote == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ steps.vars.outputs.VERSION }}"
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "[email protected]"
|
||||
git fetch origin main
|
||||
git checkout -B main origin/main
|
||||
sed -i -E "s|(image: ${IMAGE_NAME}:).*|\1${VERSION}|g" "${MANIFEST_FILE}"
|
||||
git add "${MANIFEST_FILE}"
|
||||
if git diff --cached --quiet; then
|
||||
echo "El manifiesto ya apunta a ${VERSION}."
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore(gitops): deploy ${VERSION} [skip ci]"
|
||||
git push origin HEAD:main
|
||||
|
||||
- name: Resumen del pipeline
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Versión: ${{ steps.vars.outputs.VERSION }}"
|
||||
echo "Commit: ${{ github.sha }}"
|
||||
echo "Promoción GitOps: ${{ steps.promotion.outputs.promote }}"
|
||||
echo "========================================"
|
||||
Reference in new issue
Block a user