fix ci
This commit is contained in:
parent
8d2193aaec
commit
9570316abf
3
.gitea/actionlint.yaml
Normal file
3
.gitea/actionlint.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- xpeditis-deploy
|
||||
30
.gitea/actions/security/action.yml
Normal file
30
.gitea/actions/security/action.yml
Normal file
@ -0,0 +1,30 @@
|
||||
name: Security gate
|
||||
description: Dependency, secrets, infrastructure and workflow checks for Gitea 1.22.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: '22'
|
||||
- uses: ./.gitea/actions/setup-trivy
|
||||
- name: Validate workflows and deployment checks
|
||||
shell: bash
|
||||
run: |
|
||||
archive="$RUNNER_TEMP/actionlint.tar.gz"
|
||||
curl --fail --silent --show-error --location --retry 3 --max-time 120 \
|
||||
https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz \
|
||||
--output "$archive"
|
||||
echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 $archive" | sha256sum --check --strict
|
||||
tar -xzf "$archive" -C "$RUNNER_TEMP" actionlint
|
||||
ACTIONLINT_BIN="$RUNNER_TEMP/actionlint" bash scripts/ci/validate-workflows.sh
|
||||
- name: Audit dependencies, secrets and infrastructure
|
||||
shell: bash
|
||||
run: bash scripts/ci/security-audit.sh
|
||||
- name: Save security reports on Gitea
|
||||
if: always()
|
||||
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
|
||||
with:
|
||||
name: security-reports
|
||||
path: ${{ runner.temp }}/security-reports/*.json
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
16
.gitea/actions/setup-trivy/action.yml
Normal file
16
.gitea/actions/setup-trivy/action.yml
Normal file
@ -0,0 +1,16 @@
|
||||
name: Install verified Trivy
|
||||
description: Install a pinned scanner with a checked SHA256, without elevated privileges.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
install_dir="$RUNNER_TEMP/trivy-bin"
|
||||
mkdir -p "$install_dir"
|
||||
curl --fail --silent --show-error --location --retry 3 --max-time 120 \
|
||||
https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.tar.gz \
|
||||
--output "$install_dir/trivy.tar.gz"
|
||||
echo "2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a $install_dir/trivy.tar.gz" | sha256sum --check --strict
|
||||
tar -xzf "$install_dir/trivy.tar.gz" -C "$install_dir" trivy
|
||||
echo "$install_dir" >> "$GITHUB_PATH"
|
||||
@ -19,33 +19,30 @@ name: CD Production
|
||||
#
|
||||
# 3. Le déploiement passe par SSH, pas par l'API Kubernetes.
|
||||
# L'API k3s (6443) n'est ouverte qu'aux IP d'administration. Les runners
|
||||
# GitHub n'ont pas d'IP fixe : le job ouvre le port 22 pour la seule IP du
|
||||
# Gitea n'ont pas d'IP fixe : le job ouvre le port 22 pour la seule IP du
|
||||
# runner via un firewall Hetzner dédié, puis le referme systématiquement.
|
||||
#
|
||||
# Secrets et variables : voir infra/prod/env/github-secrets.md
|
||||
# Secrets, runners et limites Gitea : voir docs/CI-CD-SECURITY.md
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "SHA court à déployer (laisser vide = HEAD de main)"
|
||||
required: false
|
||||
|
||||
concurrency:
|
||||
group: cd-production
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
REGISTRY: rg.fr-par.scw.cloud/weworkstudio
|
||||
NODE_VERSION: '20'
|
||||
NODE_VERSION: '22'
|
||||
K8S_NAMESPACE: xpeditis-prod
|
||||
|
||||
jobs:
|
||||
security:
|
||||
name: Security gate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.gitea/actions/security
|
||||
|
||||
# ═══ 1. Qualité ══════════════════════════════════════════════════════════
|
||||
backend-quality:
|
||||
name: Backend — Lint
|
||||
@ -54,14 +51,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm run lint
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run lint -- --no-fix
|
||||
|
||||
frontend-quality:
|
||||
name: Frontend — Lint & Type-check
|
||||
@ -70,12 +67,12 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/frontend/package-lock.json
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run lint
|
||||
- run: npm run type-check
|
||||
@ -88,14 +85,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm test -- --passWithNoTests
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm test -- --ci --runInBand
|
||||
|
||||
frontend-tests:
|
||||
name: Frontend — Tests unitaires
|
||||
@ -105,107 +102,68 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/frontend/package-lock.json
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm test -- --passWithNoTests
|
||||
- run: npm test -- --ci --runInBand
|
||||
|
||||
# ═══ 2. Vérification de la provenance ════════════════════════════════════
|
||||
# Si l'image preprod-SHA n'existe pas, c'est que ce commit n'est jamais passé
|
||||
# par la chaîne de preprod. Le déploiement est alors bloqué net.
|
||||
# Exige un pipeline preprod réussi ET un arbre Git identique au code testé ici.
|
||||
verify-image:
|
||||
name: Vérifier l'image de preprod
|
||||
runs-on: ubuntu-latest
|
||||
needs: [backend-tests, frontend-tests]
|
||||
needs: [security, backend-tests, frontend-tests]
|
||||
outputs:
|
||||
sha: ${{ steps.sha.outputs.short }}
|
||||
backend_digest: ${{ steps.sha.outputs.backend_digest }}
|
||||
log_exporter_digest: ${{ steps.sha.outputs.log_exporter_digest }}
|
||||
steps:
|
||||
- name: SHA court
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- name: Resolve validated preprod release
|
||||
id: sha
|
||||
run: |
|
||||
RAW="${{ github.event.inputs.tag }}"
|
||||
[ -n "$RAW" ] || RAW="${{ github.sha }}"
|
||||
echo "short=$(echo "$RAW" | cut -c1-7)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
- name: Image backend preprod-SHA présente
|
||||
run: |
|
||||
TAG="${{ env.REGISTRY }}/xpeditis-backend:preprod-${{ steps.sha.outputs.short }}"
|
||||
docker buildx imagetools inspect "$TAG" || {
|
||||
echo "::error::$TAG introuvable. Ce commit n'a pas été construit par la chaîne de preprod."
|
||||
echo "Fusionnez d'abord sur preprod et attendez que le pipeline passe au vert."
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Image log-exporter preprod-SHA présente
|
||||
run: |
|
||||
TAG="${{ env.REGISTRY }}/xpeditis-log-exporter:preprod-${{ steps.sha.outputs.short }}"
|
||||
docker buildx imagetools inspect "$TAG" || {
|
||||
echo "::error::$TAG introuvable."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ═══ 3a. Promotion du backend (aucun rebuild) ════════════════════════════
|
||||
promote-backend:
|
||||
name: Promouvoir le backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: verify-image
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- name: preprod-SHA → prod-SHA
|
||||
run: |
|
||||
SHA="${{ needs.verify-image.outputs.sha }}"
|
||||
# Opération au niveau du manifeste : aucune couche n'est retransférée,
|
||||
# le condensat de l'image reste identique à celui validé en preprod.
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ env.REGISTRY }}/xpeditis-backend:prod-${SHA} \
|
||||
--tag ${{ env.REGISTRY }}/xpeditis-backend:latest \
|
||||
${{ env.REGISTRY }}/xpeditis-backend:preprod-${SHA}
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ env.REGISTRY }}/xpeditis-log-exporter:prod-${SHA} \
|
||||
--tag ${{ env.REGISTRY }}/xpeditis-log-exporter:latest \
|
||||
${{ env.REGISTRY }}/xpeditis-log-exporter:preprod-${SHA}
|
||||
run: bash scripts/ci/resolve-release.sh
|
||||
|
||||
# ═══ 3b. Reconstruction du frontend avec les URLs de production ══════════
|
||||
build-frontend:
|
||||
name: Reconstruire le frontend (URLs de production)
|
||||
runs-on: ubuntu-latest
|
||||
needs: verify-image
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
# On construit EXACTEMENT le commit vérifié, pas HEAD.
|
||||
persist-credentials: false
|
||||
# Cet arbre Git est identique à celui de la release preprod vérifiée.
|
||||
ref: ${{ github.sha }}
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
- uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- uses: docker/build-push-action@v5
|
||||
- id: build
|
||||
uses: https://github.com/docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: ./apps/frontend
|
||||
file: ./apps/frontend/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/xpeditis-frontend:prod-${{ needs.verify-image.outputs.sha }}
|
||||
${{ env.REGISTRY }}/xpeditis-frontend:latest
|
||||
${{ env.REGISTRY }}/xpeditis-frontend:candidate-prod-${{ github.sha }}-${{ github.run_id }}
|
||||
cache-from: type=registry,ref=${{ env.REGISTRY }}/xpeditis-frontend:buildcache-prod
|
||||
cache-to: type=registry,ref=${{ env.REGISTRY }}/xpeditis-frontend:buildcache-prod,mode=max
|
||||
build-args: |
|
||||
@ -214,9 +172,12 @@ jobs:
|
||||
|
||||
- name: Contrôle — l'URL de preprod ne doit pas figurer dans le bundle
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/xpeditis-frontend:prod-${{ needs.verify-image.outputs.sha }}"
|
||||
IMAGE="${{ env.REGISTRY }}/xpeditis-frontend@${{ steps.build.outputs.digest }}"
|
||||
CID=$(docker create "$IMAGE")
|
||||
docker cp "$CID:/app/.next" /tmp/next-check 2>/dev/null || true
|
||||
trap 'docker rm "$CID" >/dev/null' EXIT
|
||||
docker cp "$CID:/app/.next" /tmp/next-check
|
||||
test -d /tmp/next-check
|
||||
trap - EXIT
|
||||
docker rm "$CID" >/dev/null
|
||||
if grep -rq "api.preprod.xpeditis.com" /tmp/next-check 2>/dev/null; then
|
||||
echo "::error::L'URL de preprod est figée dans le bundle de production."
|
||||
@ -225,25 +186,88 @@ jobs:
|
||||
fi
|
||||
echo "Aucune URL de preprod dans le bundle."
|
||||
|
||||
image-security:
|
||||
name: Image security (${{ matrix.service }}, ${{ matrix.arch }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: [verify-image, build-frontend]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
service: [backend, frontend, log-exporter]
|
||||
arch: [amd64]
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.gitea/actions/setup-trivy
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- name: Scan the exact image before deployment
|
||||
env:
|
||||
IMAGE: ${{ env.REGISTRY }}/xpeditis-${{ matrix.service }}@${{ matrix.service == 'frontend' && needs.build-frontend.outputs.digest || (matrix.service == 'backend' && needs.verify-image.outputs.backend_digest || needs.verify-image.outputs.log_exporter_digest) }}
|
||||
PLATFORM: linux/${{ matrix.arch }}
|
||||
run: |
|
||||
trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \
|
||||
--ignore-unfixed=false --exit-code 1 --timeout 15m --format json \
|
||||
--output "$RUNNER_TEMP/image-security.json" "$IMAGE"
|
||||
- name: Save image report
|
||||
if: always()
|
||||
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
|
||||
with:
|
||||
name: image-security-${{ matrix.service }}-${{ matrix.arch }}
|
||||
path: ${{ runner.temp }}/image-security.json
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
# ═══ 4. Déploiement ══════════════════════════════════════════════════════
|
||||
deploy:
|
||||
name: Déployer en production
|
||||
runs-on: ubuntu-latest
|
||||
needs: [verify-image, promote-backend, build-frontend]
|
||||
# Environnement protégé : activez « Required reviewers » pour exiger une
|
||||
# validation humaine avant toute mise en production.
|
||||
environment:
|
||||
name: production
|
||||
url: https://app.xpeditis.com
|
||||
runs-on: xpeditis-deploy
|
||||
needs: [verify-image, build-frontend, image-security]
|
||||
# Gitea 1.22 ignores environments: serialize on the dedicated deployment runner.
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- name: Publish scanned production images
|
||||
env:
|
||||
IMAGE_SHA: ${{ needs.verify-image.outputs.sha }}
|
||||
BACKEND_DIGEST: ${{ needs.verify-image.outputs.backend_digest }}
|
||||
FRONTEND_DIGEST: ${{ needs.build-frontend.outputs.digest }}
|
||||
LOG_EXPORTER_DIGEST: ${{ needs.verify-image.outputs.log_exporter_digest }}
|
||||
run: |
|
||||
for service in backend frontend log-exporter; do
|
||||
case "$service" in
|
||||
backend) digest="$BACKEND_DIGEST" ;;
|
||||
frontend) digest="$FRONTEND_DIGEST" ;;
|
||||
log-exporter) digest="$LOG_EXPORTER_DIGEST" ;;
|
||||
esac
|
||||
docker buildx imagetools create \
|
||||
--tag "$REGISTRY/xpeditis-$service:prod-$IMAGE_SHA" \
|
||||
--tag "$REGISTRY/xpeditis-$service:latest" \
|
||||
"$REGISTRY/xpeditis-$service@$digest"
|
||||
done
|
||||
|
||||
- name: Installer le client Hetzner
|
||||
run: |
|
||||
curl -fsSL https://github.com/hetznercloud/cli/releases/download/v1.49.0/hcloud-linux-amd64.tar.gz \
|
||||
| tar -xz -C /tmp hcloud
|
||||
sudo install -m 0755 /tmp/hcloud /usr/local/bin/hcloud
|
||||
hcloud version
|
||||
mkdir -p "$RUNNER_TEMP/hcloud-bin"
|
||||
curl --fail --silent --show-error --location --retry 3 --max-time 120 \
|
||||
https://github.com/hetznercloud/cli/releases/download/v1.49.0/hcloud-linux-amd64.tar.gz \
|
||||
--output "$RUNNER_TEMP/hcloud.tar.gz"
|
||||
echo "dc6e5b0e6eaf9ef2baa5473a3eb49a11e80e72cdf2a01fdf7b0af975410e79cc $RUNNER_TEMP/hcloud.tar.gz" | sha256sum --check --strict
|
||||
tar -xzf "$RUNNER_TEMP/hcloud.tar.gz" -C "$RUNNER_TEMP/hcloud-bin" hcloud
|
||||
echo "$RUNNER_TEMP/hcloud-bin" >> "$GITHUB_PATH"
|
||||
"$RUNNER_TEMP/hcloud-bin/hcloud" version
|
||||
|
||||
- name: Ouvrir le port 22 pour l'IP de ce runner
|
||||
env:
|
||||
@ -257,26 +281,29 @@ jobs:
|
||||
"protocol": "tcp",
|
||||
"port": "22",
|
||||
"source_ips": ["${RUNNER_IP}/32"],
|
||||
"description": "GitHub Actions run ${{ github.run_id }}"
|
||||
"description": "Gitea Actions run ${{ github.run_id }}"
|
||||
}]
|
||||
JSON
|
||||
hcloud firewall replace-rules "${{ vars.HCLOUD_CICD_FIREWALL }}" --rules-file /tmp/fw-open.json
|
||||
|
||||
- name: Préparer SSH
|
||||
env:
|
||||
DEPLOY_SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
|
||||
DEPLOY_KNOWN_HOSTS: ${{ secrets.PROD_SSH_KNOWN_HOSTS }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
echo "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
mkdir -p "$RUNNER_TEMP/deploy-ssh" && chmod 700 "$RUNNER_TEMP/deploy-ssh"
|
||||
printf '%s\n' "$DEPLOY_SSH_KEY" > "$RUNNER_TEMP/deploy-ssh/id_ed25519"
|
||||
chmod 600 "$RUNNER_TEMP/deploy-ssh/id_ed25519"
|
||||
# Empreinte épinglée : un détournement DNS ou BGP ne peut pas
|
||||
# rediriger le déploiement vers une machine tierce.
|
||||
echo "${{ secrets.PROD_SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
|
||||
chmod 600 ~/.ssh/known_hosts
|
||||
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$RUNNER_TEMP/deploy-ssh/known_hosts"
|
||||
chmod 600 "$RUNNER_TEMP/deploy-ssh/known_hosts"
|
||||
|
||||
- name: Synchroniser infra/prod sur le serveur
|
||||
run: |
|
||||
rsync -az --delete \
|
||||
--exclude '.terraform' --exclude '*.tfstate*' --exclude '*.tfvars' \
|
||||
-e "ssh -o StrictHostKeyChecking=yes -i ~/.ssh/id_ed25519" \
|
||||
-e "ssh -o StrictHostKeyChecking=yes -o UserKnownHostsFile=\"$RUNNER_TEMP/deploy-ssh/known_hosts\" -i \"$RUNNER_TEMP/deploy-ssh/id_ed25519\"" \
|
||||
infra/prod/ \
|
||||
"${{ secrets.PROD_SSH_USER }}@${{ secrets.PROD_SSH_HOST }}:/opt/xpeditis/infra-prod/"
|
||||
|
||||
@ -284,20 +311,21 @@ jobs:
|
||||
id: deploy
|
||||
run: |
|
||||
SHA="${{ needs.verify-image.outputs.sha }}"
|
||||
ssh -o StrictHostKeyChecking=yes -i ~/.ssh/id_ed25519 \
|
||||
ssh -o StrictHostKeyChecking=yes -o UserKnownHostsFile="$RUNNER_TEMP/deploy-ssh/known_hosts" -i "$RUNNER_TEMP/deploy-ssh/id_ed25519" \
|
||||
"${{ secrets.PROD_SSH_USER }}@${{ secrets.PROD_SSH_HOST }}" \
|
||||
"deploy prod-${SHA}"
|
||||
|
||||
- name: Tests de fumée depuis l'extérieur
|
||||
id: smoke
|
||||
env:
|
||||
PROD_API_URL: ${{ vars.PROD_API_URL }}
|
||||
PROD_APP_URL: ${{ vars.PROD_APP_URL }}
|
||||
run: bash infra/prod/scripts/smoke-test.sh
|
||||
|
||||
- name: Retour arrière si le déploiement a échoué
|
||||
if: failure() && steps.deploy.conclusion == 'failure'
|
||||
if: failure() && (steps.deploy.conclusion == 'failure' || steps.smoke.conclusion == 'failure')
|
||||
run: |
|
||||
ssh -o StrictHostKeyChecking=yes -i ~/.ssh/id_ed25519 \
|
||||
ssh -o StrictHostKeyChecking=yes -o UserKnownHostsFile="$RUNNER_TEMP/deploy-ssh/known_hosts" -i "$RUNNER_TEMP/deploy-ssh/id_ed25519" \
|
||||
"${{ secrets.PROD_SSH_USER }}@${{ secrets.PROD_SSH_HOST }}" \
|
||||
"rollback" || true
|
||||
|
||||
@ -314,7 +342,7 @@ jobs:
|
||||
|
||||
- name: Effacer la clé SSH
|
||||
if: always()
|
||||
run: shred -u ~/.ssh/id_ed25519 2>/dev/null || rm -f ~/.ssh/id_ed25519
|
||||
run: shred -u "$RUNNER_TEMP/deploy-ssh/id_ed25519" 2>/dev/null || rm -f "$RUNNER_TEMP/deploy-ssh/id_ed25519"
|
||||
|
||||
# ═══ 5. Notifications ════════════════════════════════════════════════════
|
||||
notify-success:
|
||||
@ -341,7 +369,7 @@ jobs:
|
||||
notify-failure:
|
||||
name: Notifier l'échec
|
||||
runs-on: ubuntu-latest
|
||||
needs: [backend-quality, frontend-quality, backend-tests, frontend-tests, verify-image, promote-backend, build-frontend, deploy]
|
||||
needs: [security, backend-quality, frontend-quality, backend-tests, frontend-tests, verify-image, build-frontend, image-security, deploy]
|
||||
if: failure()
|
||||
steps:
|
||||
- run: |
|
||||
@ -17,15 +17,20 @@ on:
|
||||
push:
|
||||
branches: [preprod]
|
||||
|
||||
concurrency:
|
||||
group: cd-preprod
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
REGISTRY: rg.fr-par.scw.cloud/weworkstudio
|
||||
NODE_VERSION: '20'
|
||||
NODE_VERSION: '22'
|
||||
|
||||
jobs:
|
||||
security:
|
||||
name: Security gate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.gitea/actions/security
|
||||
|
||||
# ── 1. Lint ─────────────────────────────────────────────────────────
|
||||
backend-quality:
|
||||
name: Backend — Lint
|
||||
@ -34,14 +39,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm run lint
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run lint -- --no-fix
|
||||
|
||||
frontend-quality:
|
||||
name: Frontend — Lint & Type-check
|
||||
@ -50,12 +55,12 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/frontend/package-lock.json
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run lint
|
||||
- run: npm run type-check
|
||||
@ -69,14 +74,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm test -- --passWithNoTests
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm test -- --ci --runInBand
|
||||
|
||||
frontend-tests:
|
||||
name: Frontend — Unit Tests
|
||||
@ -86,14 +91,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/frontend/package-lock.json
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm test -- --passWithNoTests
|
||||
- run: npm test -- --ci --runInBand
|
||||
|
||||
# ── 3. Integration Tests ─────────────────────────────────────────────
|
||||
integration-tests:
|
||||
@ -116,8 +121,6 @@ jobs:
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
options: >-
|
||||
@ -125,61 +128,70 @@ jobs:
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- name: Run integration tests
|
||||
env:
|
||||
NODE_ENV: test
|
||||
DATABASE_HOST: localhost
|
||||
TEST_DB_HOST: postgres
|
||||
TEST_DB_PORT: 5432
|
||||
TEST_DB_USER: xpeditis_test
|
||||
TEST_DB_PASSWORD: xpeditis_test_password
|
||||
TEST_DB_NAME: xpeditis_test
|
||||
DATABASE_HOST: postgres
|
||||
DATABASE_PORT: 5432
|
||||
DATABASE_USER: xpeditis_test
|
||||
DATABASE_PASSWORD: xpeditis_test_password
|
||||
DATABASE_NAME: xpeditis_test
|
||||
DATABASE_SYNCHRONIZE: 'false'
|
||||
REDIS_HOST: localhost
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_PASSWORD: ''
|
||||
JWT_SECRET: test-secret-key-ci
|
||||
SMTP_HOST: localhost
|
||||
SMTP_PORT: 1025
|
||||
SMTP_FROM: test@xpeditis.com
|
||||
run: npm run test:integration -- --passWithNoTests
|
||||
run: npm run test:integration -- --ci --runInBand
|
||||
|
||||
# ── 4. Docker Build & Push ───────────────────────────────────────────
|
||||
# Tags: preprod (latest for this env) + preprod-SHA (used by prod for exact promotion)
|
||||
build-backend:
|
||||
name: Build Backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: integration-tests
|
||||
needs: [security, integration-tests]
|
||||
outputs:
|
||||
sha: ${{ steps.sha.outputs.short }}
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Short SHA
|
||||
id: sha
|
||||
run: echo "short=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
- uses: https://github.com/docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- uses: docker/build-push-action@v5
|
||||
- id: build
|
||||
uses: https://github.com/docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: ./apps/backend
|
||||
file: ./apps/backend/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/xpeditis-backend:preprod
|
||||
${{ env.REGISTRY }}/xpeditis-backend:preprod-${{ steps.sha.outputs.short }}
|
||||
cache-from: type=registry,ref=${{ env.REGISTRY }}/xpeditis-backend:buildcache
|
||||
cache-to: type=registry,ref=${{ env.REGISTRY }}/xpeditis-backend:buildcache,mode=max
|
||||
@ -188,27 +200,33 @@ jobs:
|
||||
build-frontend:
|
||||
name: Build Frontend
|
||||
runs-on: ubuntu-latest
|
||||
needs: integration-tests
|
||||
needs: [security, integration-tests]
|
||||
outputs:
|
||||
sha: ${{ steps.sha.outputs.short }}
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Short SHA
|
||||
id: sha
|
||||
run: echo "short=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
- uses: https://github.com/docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- uses: docker/build-push-action@v5
|
||||
- id: build
|
||||
uses: https://github.com/docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: ./apps/frontend
|
||||
file: ./apps/frontend/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/xpeditis-frontend:preprod
|
||||
${{ env.REGISTRY }}/xpeditis-frontend:preprod-${{ steps.sha.outputs.short }}
|
||||
cache-from: type=registry,ref=${{ env.REGISTRY }}/xpeditis-frontend:buildcache
|
||||
cache-to: type=registry,ref=${{ env.REGISTRY }}/xpeditis-frontend:buildcache,mode=max
|
||||
@ -220,42 +238,108 @@ jobs:
|
||||
build-log-exporter:
|
||||
name: Build Log Exporter
|
||||
runs-on: ubuntu-latest
|
||||
needs: integration-tests
|
||||
needs: [security, integration-tests]
|
||||
outputs:
|
||||
sha: ${{ steps.sha.outputs.short }}
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Short SHA
|
||||
id: sha
|
||||
run: echo "short=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
- uses: https://github.com/docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- uses: docker/build-push-action@v5
|
||||
- id: build
|
||||
uses: https://github.com/docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: ./apps/log-exporter
|
||||
file: ./apps/log-exporter/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/xpeditis-log-exporter:preprod
|
||||
${{ env.REGISTRY }}/xpeditis-log-exporter:preprod-${{ steps.sha.outputs.short }}
|
||||
cache-from: type=registry,ref=${{ env.REGISTRY }}/xpeditis-log-exporter:buildcache
|
||||
cache-to: type=registry,ref=${{ env.REGISTRY }}/xpeditis-log-exporter:buildcache,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
image-security:
|
||||
name: Image security (${{ matrix.service }}, ${{ matrix.arch }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-backend, build-frontend, build-log-exporter]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
service: [backend, frontend, log-exporter]
|
||||
arch: [amd64, arm64]
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.gitea/actions/setup-trivy
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- name: Scan the exact image before deployment
|
||||
env:
|
||||
IMAGE: ${{ env.REGISTRY }}/xpeditis-${{ matrix.service }}@${{ needs[format('build-{0}', matrix.service)].outputs.digest }}
|
||||
PLATFORM: linux/${{ matrix.arch }}
|
||||
run: |
|
||||
trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \
|
||||
--ignore-unfixed=false --exit-code 1 --timeout 15m --format json \
|
||||
--output "$RUNNER_TEMP/image-security.json" "$IMAGE"
|
||||
- name: Save image report
|
||||
if: always()
|
||||
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
|
||||
with:
|
||||
name: image-security-${{ matrix.service }}-${{ matrix.arch }}
|
||||
path: ${{ runner.temp }}/image-security.json
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
# ── 5. Deploy via Portainer ──────────────────────────────────────────
|
||||
deploy:
|
||||
name: Deploy to Preprod
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-backend, build-frontend, build-log-exporter]
|
||||
environment: preprod
|
||||
runs-on: xpeditis-deploy
|
||||
needs: [build-backend, build-frontend, build-log-exporter, image-security]
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
- uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: nologin
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- name: Publish scanned preprod images
|
||||
env:
|
||||
BACKEND_DIGEST: ${{ needs.build-backend.outputs.digest }}
|
||||
FRONTEND_DIGEST: ${{ needs.build-frontend.outputs.digest }}
|
||||
LOG_EXPORTER_DIGEST: ${{ needs.build-log-exporter.outputs.digest }}
|
||||
run: |
|
||||
for service in backend frontend log-exporter; do
|
||||
case "$service" in
|
||||
backend) digest="$BACKEND_DIGEST" ;;
|
||||
frontend) digest="$FRONTEND_DIGEST" ;;
|
||||
log-exporter) digest="$LOG_EXPORTER_DIGEST" ;;
|
||||
esac
|
||||
docker buildx imagetools create \
|
||||
--tag "$REGISTRY/xpeditis-$service:preprod" \
|
||||
"$REGISTRY/xpeditis-$service@$digest"
|
||||
done
|
||||
- name: Deploy backend
|
||||
run: |
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "${{ secrets.PORTAINER_WEBHOOK_BACKEND }}")
|
||||
HTTP_CODE=$(curl --connect-timeout 10 --max-time 30 -s -o /dev/null -w "%{http_code}" -X POST "${{ secrets.PORTAINER_WEBHOOK_BACKEND }}")
|
||||
echo "Portainer response: HTTP $HTTP_CODE"
|
||||
if [[ "$HTTP_CODE" != "2"* ]]; then
|
||||
echo "ERROR: Portainer webhook failed with HTTP $HTTP_CODE"
|
||||
@ -266,7 +350,7 @@ jobs:
|
||||
run: sleep 20
|
||||
- name: Deploy frontend
|
||||
run: |
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST "${{ secrets.PORTAINER_WEBHOOK_FRONTEND }}")
|
||||
HTTP_CODE=$(curl --connect-timeout 10 --max-time 30 -s -o /dev/null -w "%{http_code}" -X POST "${{ secrets.PORTAINER_WEBHOOK_FRONTEND }}")
|
||||
echo "Portainer response: HTTP $HTTP_CODE"
|
||||
if [[ "$HTTP_CODE" != "2"* ]]; then
|
||||
echo "ERROR: Portainer webhook failed with HTTP $HTTP_CODE"
|
||||
@ -274,6 +358,27 @@ jobs:
|
||||
fi
|
||||
echo "Frontend webhook triggered."
|
||||
|
||||
- name: Verify backend health
|
||||
env:
|
||||
BASE_URL: ${{ secrets.PREPROD_BACKEND_URL }}
|
||||
run: bash scripts/ci/health-check.sh "${BASE_URL:?Missing PREPROD_BACKEND_URL}/api/v1/health"
|
||||
- name: Verify frontend health
|
||||
env:
|
||||
BASE_URL: ${{ secrets.PREPROD_FRONTEND_URL }}
|
||||
run: bash scripts/ci/health-check.sh "${BASE_URL:?Missing PREPROD_FRONTEND_URL}"
|
||||
|
||||
- name: Mark successfully deployed preprod images
|
||||
env:
|
||||
BACKEND_DIGEST: ${{ needs.build-backend.outputs.digest }}
|
||||
LOG_EXPORTER_DIGEST: ${{ needs.build-log-exporter.outputs.digest }}
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
--tag "$REGISTRY/xpeditis-backend:validated-preprod-$GITHUB_SHA" \
|
||||
"$REGISTRY/xpeditis-backend@$BACKEND_DIGEST"
|
||||
docker buildx imagetools create \
|
||||
--tag "$REGISTRY/xpeditis-log-exporter:validated-preprod-$GITHUB_SHA" \
|
||||
"$REGISTRY/xpeditis-log-exporter@$LOG_EXPORTER_DIGEST"
|
||||
|
||||
# ── Notifications ────────────────────────────────────────────────────
|
||||
notify-success:
|
||||
name: Notify Success
|
||||
@ -298,14 +403,14 @@ jobs:
|
||||
notify-failure:
|
||||
name: Notify Failure
|
||||
runs-on: ubuntu-latest
|
||||
needs: [backend-quality, frontend-quality, backend-tests, frontend-tests, integration-tests, build-backend, build-frontend, deploy]
|
||||
needs: [security, backend-quality, frontend-quality, backend-tests, frontend-tests, integration-tests, build-backend, build-frontend, build-log-exporter, image-security, deploy]
|
||||
if: failure()
|
||||
steps:
|
||||
- run: |
|
||||
curl -s -H "Content-Type: application/json" -d '{
|
||||
"embeds": [{
|
||||
"title": "❌ Preprod Pipeline Failed",
|
||||
"description": "Preprod was NOT deployed.",
|
||||
"description": "Pipeline en échec. Vérifiez les rapports et l état réel des services avant de relancer.",
|
||||
"color": 15158332,
|
||||
"fields": [
|
||||
{"name": "Author", "value": "${{ github.actor }}", "inline": true},
|
||||
@ -6,14 +6,19 @@ on:
|
||||
pull_request:
|
||||
branches: [dev]
|
||||
|
||||
concurrency:
|
||||
group: dev-ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
NODE_VERSION: '22'
|
||||
|
||||
jobs:
|
||||
security:
|
||||
name: Security gate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.gitea/actions/security
|
||||
|
||||
backend-quality:
|
||||
name: Backend — Lint
|
||||
runs-on: ubuntu-latest
|
||||
@ -21,14 +26,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm run lint
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run lint -- --no-fix
|
||||
|
||||
frontend-quality:
|
||||
name: Frontend — Lint & Type-check
|
||||
@ -37,12 +42,12 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/frontend/package-lock.json
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run lint
|
||||
- run: npm run type-check
|
||||
@ -55,14 +60,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm test -- --passWithNoTests
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm test -- --ci --runInBand
|
||||
|
||||
frontend-tests:
|
||||
name: Frontend — Unit Tests
|
||||
@ -72,19 +77,19 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/frontend/package-lock.json
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm test -- --passWithNoTests
|
||||
- run: npm test -- --ci --runInBand
|
||||
|
||||
notify-failure:
|
||||
name: Notify Failure
|
||||
runs-on: ubuntu-latest
|
||||
needs: [backend-quality, frontend-quality, backend-tests, frontend-tests]
|
||||
needs: [security, backend-quality, frontend-quality, backend-tests, frontend-tests]
|
||||
if: failure()
|
||||
steps:
|
||||
- name: Discord
|
||||
@ -2,20 +2,25 @@ name: PR Checks
|
||||
|
||||
# Required status checks — configure these in branch protection rules.
|
||||
# PRs to preprod : lint + type-check + unit tests + integration tests
|
||||
# PRs to main : lint + type-check + unit tests only
|
||||
# PRs to main : same checks, including integration and security
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [preprod, main]
|
||||
|
||||
concurrency:
|
||||
group: pr-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
NODE_VERSION: '22'
|
||||
|
||||
jobs:
|
||||
security:
|
||||
name: Security gate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.gitea/actions/security
|
||||
|
||||
backend-quality:
|
||||
name: Backend — Lint
|
||||
runs-on: ubuntu-latest
|
||||
@ -23,14 +28,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm run lint
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run lint -- --no-fix
|
||||
|
||||
frontend-quality:
|
||||
name: Frontend — Lint & Type-check
|
||||
@ -39,12 +44,12 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/frontend/package-lock.json
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm run lint
|
||||
- run: npm run type-check
|
||||
@ -57,14 +62,14 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm test -- --passWithNoTests
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm test -- --ci --runInBand
|
||||
|
||||
frontend-tests:
|
||||
name: Frontend — Unit Tests
|
||||
@ -74,22 +79,20 @@ jobs:
|
||||
run:
|
||||
working-directory: apps/frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/frontend/package-lock.json
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- run: npm test -- --passWithNoTests
|
||||
- run: npm test -- --ci --runInBand
|
||||
|
||||
# Integration tests — PRs to preprod only
|
||||
# Code going to main was already integration-tested when it passed through preprod
|
||||
# Integration tests validate the actual merge candidate for both branches.
|
||||
integration-tests:
|
||||
name: Backend — Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: backend-tests
|
||||
if: github.base_ref == 'preprod'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/backend
|
||||
@ -106,8 +109,6 @@ jobs:
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
options: >-
|
||||
@ -115,31 +116,34 @@ jobs:
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/backend/package-lock.json
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm ci --legacy-peer-deps
|
||||
- name: Run integration tests
|
||||
env:
|
||||
NODE_ENV: test
|
||||
DATABASE_HOST: localhost
|
||||
TEST_DB_HOST: postgres
|
||||
TEST_DB_PORT: 5432
|
||||
TEST_DB_USER: xpeditis_test
|
||||
TEST_DB_PASSWORD: xpeditis_test_password
|
||||
TEST_DB_NAME: xpeditis_test
|
||||
DATABASE_HOST: postgres
|
||||
DATABASE_PORT: 5432
|
||||
DATABASE_USER: xpeditis_test
|
||||
DATABASE_PASSWORD: xpeditis_test_password
|
||||
DATABASE_NAME: xpeditis_test
|
||||
DATABASE_SYNCHRONIZE: 'false'
|
||||
REDIS_HOST: localhost
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_PASSWORD: ''
|
||||
JWT_SECRET: test-secret-key-ci
|
||||
SMTP_HOST: localhost
|
||||
SMTP_PORT: 1025
|
||||
SMTP_FROM: test@xpeditis.com
|
||||
run: npm run test:integration -- --passWithNoTests
|
||||
run: npm run test:integration -- --ci --runInBand
|
||||
@ -1,7 +1,7 @@
|
||||
# ===============================================
|
||||
# Stage 1: Dependencies Installation
|
||||
# ===============================================
|
||||
FROM node:20-alpine AS dependencies
|
||||
FROM node:22-alpine AS dependencies
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache python3 make g++ libc6-compat
|
||||
@ -19,7 +19,7 @@ RUN npm ci --legacy-peer-deps
|
||||
# ===============================================
|
||||
# Stage 2: Build Application
|
||||
# ===============================================
|
||||
FROM node:20-alpine AS builder
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -38,7 +38,7 @@ RUN npm prune --production --legacy-peer-deps
|
||||
# ===============================================
|
||||
# Stage 3: Production Image
|
||||
# ===============================================
|
||||
FROM node:20-alpine AS production
|
||||
FROM node:22-alpine AS production
|
||||
|
||||
# Install dumb-init for proper signal handling
|
||||
RUN apk add --no-cache dumb-init
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# ===============================================
|
||||
# Stage 1: Dependencies Installation
|
||||
# ===============================================
|
||||
FROM node:20-alpine AS dependencies
|
||||
FROM node:22-alpine AS dependencies
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache libc6-compat
|
||||
@ -18,7 +18,7 @@ RUN npm ci --legacy-peer-deps
|
||||
# ===============================================
|
||||
# Stage 2: Build Application
|
||||
# ===============================================
|
||||
FROM node:20-alpine AS builder
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -48,7 +48,7 @@ RUN npm run build
|
||||
# ===============================================
|
||||
# Stage 3: Production Image
|
||||
# ===============================================
|
||||
FROM node:20-alpine AS production
|
||||
FROM node:22-alpine AS production
|
||||
|
||||
# Install dumb-init for proper signal handling
|
||||
RUN apk add --no-cache dumb-init curl
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
FROM node:20-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
RUN npm install --omit=dev
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
COPY src/ ./src/
|
||||
|
||||
|
||||
965
apps/log-exporter/package-lock.json
generated
Normal file
965
apps/log-exporter/package-lock.json
generated
Normal file
@ -0,0 +1,965 @@
|
||||
{
|
||||
"name": "xpeditis-log-exporter",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "xpeditis-log-exporter",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"json2csv": "^6.0.0-alpha.2",
|
||||
"node-fetch": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@streamparser/json": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.6.tgz",
|
||||
"integrity": "sha512-vL9EVn/v+OhZ+Wcs6O4iKE9EUpwHUqHmCtNUMWjqp+6dr85+XPOSGTEsqYNq1Vn04uk9SWlOVmx9J48ggJVT2Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.8",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz",
|
||||
"integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "~3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "~1.2.0",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"on-finished": "~2.4.1",
|
||||
"qs": "~6.16.0",
|
||||
"raw-body": "~2.5.3",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.22.3",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz",
|
||||
"integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "~1.20.5",
|
||||
"content-disposition": "~0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "~0.7.1",
|
||||
"cookie-signature": "~1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.3.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "~0.1.13",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "~6.16.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "~0.19.0",
|
||||
"serve-static": "~1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "~2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
||||
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~2.0.2",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/json2csv": {
|
||||
"version": "6.0.0-alpha.2",
|
||||
"resolved": "https://registry.npmjs.org/json2csv/-/json2csv-6.0.0-alpha.2.tgz",
|
||||
"integrity": "sha512-nJ3oP6QxN8z69IT1HmrJdfVxhU1kLTBVgMfRnNZc37YEY+jZ4nU27rBGxT4vaqM/KUCavLRhntmTuBFqZLBUcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@streamparser/json": "^0.0.6",
|
||||
"commander": "^6.2.0",
|
||||
"lodash.get": "^4.4.2"
|
||||
},
|
||||
"bin": {
|
||||
"json2csv": "bin/json2csv.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12",
|
||||
"npm": ">= 6.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
|
||||
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
||||
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
|
||||
"integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
|
||||
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.1",
|
||||
"side-channel": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
||||
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "~3.1.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
||||
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "~2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.16.3",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
|
||||
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "~0.19.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.4",
|
||||
"side-channel-list": "^1.0.1",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
||||
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
docs/CI-CD-SECURITY.md
Normal file
161
docs/CI-CD-SECURITY.md
Normal file
@ -0,0 +1,161 @@
|
||||
# CI/CD sécurisée — Gitea 1.22.6
|
||||
|
||||
La version du serveur `https://gitea.ops.xpeditis.com` a été vérifiée via
|
||||
`/api/v1/version` : **1.22.6**. Les pipelines ciblent Gitea Actions / act_runner,
|
||||
avec des jobs exécutés dans des conteneurs Linux AMD64.
|
||||
|
||||
## Workflows et contrôles
|
||||
|
||||
Les quatre workflows actifs se trouvent dans `.gitea/workflows/`. Les anciennes
|
||||
copies `.github/workflows/` sont déplacées pour éviter une double exécution.
|
||||
L'action composite `.gitea/actions/security` est appelée directement par chaque
|
||||
pipeline ; aucun workflow réutilisable GitHub ni client `gh` n'est nécessaire.
|
||||
|
||||
| Pipeline | Déclenchement | Contrôles |
|
||||
| --- | --- | --- |
|
||||
| Dev CI | Push/PR vers `dev` | Lint, types, tests unitaires, sécurité |
|
||||
| PR Checks | PR vers `preprod` ou `main` | Idem + tests d'intégration PostgreSQL/Redis |
|
||||
| CD Preprod | Push vers `preprod` | Idem + build, scan AMD64/ARM64, déploiement, contrôles HTTP |
|
||||
| CD Production | Push vers `main` | Qualité/tests/sécurité, provenance préprod, build frontend, scan AMD64, déploiement SSH, tests de fumée |
|
||||
|
||||
Le job **Security gate** exécute :
|
||||
|
||||
- `npm audit --package-lock-only --audit-level=high` sur la racine, le backend,
|
||||
le frontend et le log-exporter, sans installer de dépendances ni exécuter leurs
|
||||
scripts. Les dépendances de développement sont incluses.
|
||||
- Trivy sur les fichiers suivis du commit : détection de secrets et de mauvaises
|
||||
configurations Docker/Kubernetes/Terraform, seuil HIGH/CRITICAL.
|
||||
- La validation statique des workflows et les tests des scripts de promotion/santé.
|
||||
|
||||
Les images sont analysées par digest, avec HIGH/CRITICAL bloquants, y compris
|
||||
lorsqu'aucun correctif n'est disponible. Les erreurs de scanner ou de registre
|
||||
échouent aussi : aucune exclusion générale ni échec masqué n'est ajouté.
|
||||
Les installations applicatives utilisent `npm ci`, le runtime est Node 22 et les
|
||||
actions sont épinglées par SHA. Trivy, Actionlint et Hetzner CLI sont téléchargés
|
||||
avec vérification SHA256. Le lint backend ne réécrit plus les fichiers.
|
||||
|
||||
Les rapports sont joints aux exécutions **Gitea Actions** avec
|
||||
`actions/upload-artifact` **v3**, compatible avec le protocole de cette version
|
||||
Gitea. `security-reports` est conservé 7 jours, `image-security-*` 14 jours selon
|
||||
la politique de rétention du serveur. Les valeurs et extraits de code des secrets
|
||||
sont retirés du rapport téléchargeable. Les URLs GitHub servent uniquement à
|
||||
récupérer les actions publiques épinglées, pas à exécuter les pipelines.
|
||||
|
||||
Ces scans ne remplacent pas un audit du code métier ni un test d'intrusion ; la
|
||||
recherche de secrets porte sur le commit courant, pas tout l'historique.
|
||||
|
||||
## Promotion sans API GitHub
|
||||
|
||||
Gitea 1.22.6 n'expose pas d'API de consultation des runs Actions dans son Swagger.
|
||||
La provenance utilise donc le registre Scaleway existant : après scans, webhooks
|
||||
et contrôles HTTP backend/frontend réussis, le job préprod publie des marqueurs
|
||||
`validated-preprod-<SHA complet>` pour le backend et le log-exporter, à partir des
|
||||
digests construits et scannés. Le log-exporter est construit/scanné par cette
|
||||
chaîne ; son déploiement n'a pas de webhook ni de contrôle HTTP dédié existant.
|
||||
|
||||
La production accepte seulement un commit de main ou un de ses parents dont
|
||||
l'arbre Git est identique, avec les deux marqueurs présents. Elle récupère leurs
|
||||
digests, les rescane et les promeut sans reconstruire le backend/log-exporter.
|
||||
Le frontend est reconstruit avec les URLs prod puis scanné par digest. Les tags
|
||||
`prod-SHA` et `latest` ne sont publiés que dans le job de déploiement, après scans.
|
||||
|
||||
Utiliser un merge classique ou fast-forward de préprod vers main ; un squash ou
|
||||
rebase qui supprime cette provenance sera refusé. Il faut une première préprod
|
||||
réussie avec ces nouveaux workflows avant de promouvoir en production. La preuve
|
||||
repose sur les droits du registre : réserver l'écriture des marqueurs au compte CI
|
||||
et ne pas les créer manuellement. Ce n'est pas une attestation cryptographique.
|
||||
|
||||
Les images candidates peuvent rester dans le registre après un échec de scan,
|
||||
sans être publiées sous les alias d'environnement par le pipeline.
|
||||
|
||||
## Configuration nécessaire sur ton instance
|
||||
|
||||
Gitea 1.22 ignore notamment `concurrency`, `permissions`, `environment`, les délais
|
||||
YAML de jobs et `workflow_dispatch`. Ces paramètres ne sont donc pas présentés
|
||||
comme des protections effectives dans les workflows adaptés.
|
||||
|
||||
1. Activer Actions dans le dépôt et disposer d'un runner Docker Linux AMD64 avec
|
||||
le label `ubuntu-latest`, Git, Bash, Python 3, curl, tar, timeout et les outils
|
||||
Docker. Les builds multiarchitecture ont besoin du support QEMU/binfmt.
|
||||
Les services d'intégration sont joints via `postgres` et `redis`, sans ports
|
||||
hôte fixes ; un runner en mode host n'est pas la cible de ces workflows.
|
||||
2. Enregistrer **un seul runner** avec le label **`xpeditis-deploy`**, une capacité
|
||||
**1**, et une limite d'exécution adaptée (par exemple 1 h). Les deux jobs de
|
||||
déploiement utilisent ce label : publication des alias, déploiement, santé et
|
||||
fermeture du firewall restent dans le même job. Ne pas donner ce label à
|
||||
plusieurs runners. Le runner doit être isolé des jobs de PR et disposer de
|
||||
Docker, Git, curl, SSH et rsync. Sans ce runner, les déploiements restent en attente.
|
||||
3. Protéger `dev`, `preprod`, `main` dans les réglages de branches Gitea : interdire
|
||||
les push directs/force-push et exiger **Security gate**, les deux contrôles
|
||||
qualité et les deux tests unitaires ; ajouter l'intégration pour preprod/main.
|
||||
Choisir les noms exacts proposés après la première exécution. Les approbations
|
||||
humaines doivent être imposées sur les PR, pas via `environment`.
|
||||
4. Renseigner les secrets/variables dans **Settings → Actions** du dépôt Gitea :
|
||||
registre, webhooks Portainer, URLs préprod, URLs de build et identifiants
|
||||
SSH/Hetzner déjà référencés. Les secrets ne sont pas des secrets d'environnement
|
||||
GitHub. Les clés SSH temporaires sont écrites dans le répertoire du job.
|
||||
5. Les fonctions manuelles `workflow_dispatch` ne sont pas disponibles sur 1.22.
|
||||
L'ancien workflow de rollback est conservé hors du dossier actif dans
|
||||
`.gitea/manual/rollback.reference.yml` comme référence, sans prétendre qu'il est
|
||||
exécutable sur cette version. Le rollback SSH automatique de production reste
|
||||
actif en cas d'échec du déploiement ou des tests de fumée. Pour une intervention
|
||||
manuelle, utiliser la procédure serveur existante ; ne pas ajouter un faux
|
||||
bouton de lancement Gitea.
|
||||
|
||||
Ces réglages serveur/runners n'ont pas été modifiés depuis cette session. Aucun
|
||||
pipeline distant ni déploiement n'a été lancé.
|
||||
|
||||
## Mises à jour de dépendances
|
||||
|
||||
Dependabot a été remplacé par `renovate.json`. La configuration propose des PR
|
||||
vers `dev` pour npm, Docker et les actions, sans fusion automatique. Elle n'installe
|
||||
ni ne démarre un bot. Un service Renovate doit être configuré séparément avec
|
||||
`RENOVATE_PLATFORM=gitea`, `RENOVATE_ENDPOINT=https://gitea.ops.xpeditis.com/api/v1`,
|
||||
un compte bot et son token, et le dépôt `David/xpeditis2.0`. Conserver le token hors
|
||||
du dépôt. Les hashes des outils téléchargés dans les scripts doivent être mis à
|
||||
jour en même temps que leurs versions.
|
||||
|
||||
## Alertes déjà constatées
|
||||
|
||||
Audits npm des lockfiles au 22 septembre 2026 :
|
||||
|
||||
| Projet | HIGH | CRITICAL |
|
||||
| --- | ---: | ---: |
|
||||
| Racine | 0 | 0 |
|
||||
| Backend | 50 | 0 |
|
||||
| Frontend | 13 | 1 |
|
||||
| Log-exporter | 0 | 0 |
|
||||
|
||||
Ce sont des entrées de dépendances signalées, pas nécessairement autant de CVE
|
||||
distinctes. L'entrée critique frontend concerne `next` ; npm propose une migration
|
||||
majeure. Aucune mise à jour applicative forcée n'a été faite pour masquer ces résultats.
|
||||
|
||||
Le scan local des configurations a relevé 11 alertes Kubernetes HIGH : systèmes
|
||||
de fichiers inscriptibles, droits de monitoring et accès hôte. Elles restent à
|
||||
examiner et bloquantes. Certains accès peuvent être nécessaires au monitoring.
|
||||
|
||||
Les secrets Stripe en dur de la stack préprod ont été remplacés par les variables
|
||||
Portainer obligatoires `STRIPE_SECRET_KEY` et `STRIPE_WEBHOOK_SECRET`. Renseigner
|
||||
ces variables avant redéploiement. Si les anciennes valeurs sont actives, les
|
||||
révoquer/renouveler dans Stripe : elles restent dans l'historique Git. Le scan de
|
||||
ces configurations ne signale plus ce secret après modification.
|
||||
|
||||
## Validation
|
||||
|
||||
Les 19 tests offline de promotion et santé passent : arbre différent, marqueurs
|
||||
manquants, panne du registre, digest invalide, mauvaise branche, saisie hostile et
|
||||
échec HTTP sont refusés. Les tests vérifient aussi que les échecs et délais des
|
||||
audits restent bloquants et que le rapport masque les valeurs de secrets. Actionlint valide la syntaxe après normalisation des URLs
|
||||
d'actions Gitea ; ce contrôle ne remplace pas une exécution avec act_runner.
|
||||
|
||||
```bash
|
||||
ACTIONLINT_BIN=/chemin/vers/actionlint bash scripts/ci/validate-workflows.sh
|
||||
```
|
||||
|
||||
Les installations ont été vérifiées avec `npm ci --dry-run --offline
|
||||
--ignore-scripts --legacy-peer-deps`. Les builds Docker, les tests applicatifs sous
|
||||
Node 22, le transport réel des artefacts et les déploiements sont encore à valider
|
||||
sur les runners de l'instance.
|
||||
|
||||
Références : [différences Gitea 1.22](https://docs.gitea.com/1.22/usage/actions/comparison/),
|
||||
[Renovate sur Gitea](https://docs.renovatebot.com/modules/platform/gitea/).
|
||||
8
renovate.json
Normal file
8
renovate.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"],
|
||||
"baseBranchPatterns": ["dev"],
|
||||
"enabledManagers": ["npm", "dockerfile", "github-actions"],
|
||||
"automerge": false,
|
||||
"prConcurrentLimit": 5
|
||||
}
|
||||
13
scripts/ci/health-check.sh
Normal file
13
scripts/ci/health-check.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
url="${1:?A health-check URL is required}"
|
||||
[[ "$url" == https://* ]] || { echo 'Health check requires HTTPS'; exit 1; }
|
||||
for attempt in {1..12}; do
|
||||
status=$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
|
||||
--connect-timeout 5 --max-time 10 "$url") || status=000
|
||||
echo "Health check attempt $attempt: HTTP $status"
|
||||
if [[ "$status" == 200 ]]; then exit 0; fi
|
||||
sleep 10
|
||||
done
|
||||
echo '::error::Service did not become healthy after deployment.'
|
||||
exit 1
|
||||
43
scripts/ci/resolve-release.sh
Normal file
43
scripts/ci/resolve-release.sh
Normal file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# Resolve a successfully validated preprod commit with the exact production tree.
|
||||
set -euo pipefail
|
||||
: "${GITHUB_SHA:?}" "${REGISTRY:?}" "${GITHUB_OUTPUT:?}"
|
||||
[[ "${GITHUB_REF:-}" == refs/heads/main ]] || { echo '::error::Production must run from main.'; exit 1; }
|
||||
production_tree=$(git rev-parse "$GITHUB_SHA^{tree}")
|
||||
if [[ -n "${REQUESTED_SHA:-}" ]]; then
|
||||
[[ "$REQUESTED_SHA" =~ ^[0-9a-fA-F]{7,40}$ ]] || { echo '::error::Invalid commit SHA.'; exit 1; }
|
||||
candidates=$(git rev-parse --verify "$REQUESTED_SHA^{commit}")
|
||||
else
|
||||
# A normal merge creates a new SHA: its second parent is preprod.
|
||||
candidates=$(git rev-list --no-walk "$GITHUB_SHA" $(git show -s --format=%P "$GITHUB_SHA"))
|
||||
fi
|
||||
for candidate in $candidates; do
|
||||
git merge-base --is-ancestor "$candidate" "$GITHUB_SHA" || continue
|
||||
[[ "$(git rev-parse "$candidate^{tree}")" == "$production_tree" ]] || continue
|
||||
valid=true
|
||||
digests=()
|
||||
for service in backend log-exporter; do
|
||||
# Only the preprod deployment job publishes these markers after health checks.
|
||||
image="$REGISTRY/xpeditis-$service:validated-preprod-$candidate"
|
||||
if ! manifest=$(docker buildx imagetools inspect "$image"); then
|
||||
valid=false
|
||||
break
|
||||
fi
|
||||
digest=$(awk '/^Digest:/ {print $2; exit}' <<< "$manifest")
|
||||
if [[ ! "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
||||
echo 'Invalid manifest digest returned by the registry.' >&2
|
||||
exit 1
|
||||
fi
|
||||
digests+=("$digest")
|
||||
done
|
||||
if [[ "$valid" == true ]]; then
|
||||
echo "short=${candidate:0:7}" >> "$GITHUB_OUTPUT"
|
||||
echo "commit=$candidate" >> "$GITHUB_OUTPUT"
|
||||
echo "backend_digest=${digests[0]}" >> "$GITHUB_OUTPUT"
|
||||
echo "log_exporter_digest=${digests[1]}" >> "$GITHUB_OUTPUT"
|
||||
echo "Validated preprod commit: $candidate (identical source tree to production)"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
echo '::error::No validated preprod image pair matches this production tree. Merge preprod without squash/rebase, or provide its validated SHA.'
|
||||
exit 1
|
||||
33
scripts/ci/security-audit.sh
Normal file
33
scripts/ci/security-audit.sh
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
reports="${RUNNER_TEMP:?}/security-reports"
|
||||
mkdir -p "$reports"
|
||||
failed=0
|
||||
for project in root backend frontend log-exporter; do
|
||||
directory=.
|
||||
[[ "$project" == root ]] || directory="apps/$project"
|
||||
if ! (cd "$directory" && timeout 10m npm audit --package-lock-only --audit-level=high --json) > "$reports/npm-audit-$project.json"; then
|
||||
echo "Dependency audit failed: $project (see report)"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
source_dir=$(mktemp -d "$RUNNER_TEMP/security-source.XXXXXX")
|
||||
git archive HEAD | tar -x -C "$source_dir"
|
||||
if ! trivy fs --scanners secret,misconfig --severity HIGH,CRITICAL --exit-code 1 \
|
||||
--timeout 10m --format json --output "$RUNNER_TEMP/source-security-raw.json" "$source_dir"; then
|
||||
failed=1
|
||||
fi
|
||||
python3 - <<'PYTHON'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
raw = Path(os.environ['RUNNER_TEMP']) / 'source-security-raw.json'
|
||||
if not raw.exists():
|
||||
raise SystemExit('Source scanner produced no report')
|
||||
report = json.loads(raw.read_text())
|
||||
for result in report.get('Results', []):
|
||||
for secret in result.get('Secrets', []):
|
||||
secret.pop('Match', None)
|
||||
secret.pop('Code', None)
|
||||
(raw.parent / 'security-reports' / 'source-security.json').write_text(json.dumps(report))
|
||||
PYTHON
|
||||
exit "$failed"
|
||||
188
scripts/ci/test_release_checks.py
Normal file
188
scripts/ci/test_release_checks.py
Normal file
@ -0,0 +1,188 @@
|
||||
"""Offline behavioral checks for deployment safety scripts (stdlib only)."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
SCRIPTS = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
class ReleaseChecks(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
self.bin = self.root / 'bin'
|
||||
self.bin.mkdir()
|
||||
self.env = dict(os.environ, PATH=f'{self.bin}:{os.environ["PATH"]}',
|
||||
REGISTRY='registry.example.invalid/test', GITHUB_REF='refs/heads/main',
|
||||
GITHUB_OUTPUT=str(self.root / 'output'), REQUESTED_SHA='')
|
||||
self.git('init', '-q', '-b', 'main')
|
||||
self.git('config', 'user.email', 'ci@example.invalid')
|
||||
self.git('config', 'user.name', 'CI Test')
|
||||
self.commit('initial')
|
||||
self.git('checkout', '-q', '-b', 'preprod')
|
||||
self.commit('release')
|
||||
self.preprod = self.git('rev-parse', 'HEAD')
|
||||
self.git('checkout', '-q', 'main')
|
||||
self.git('merge', '-q', '--no-ff', 'preprod', '-m', 'Promote')
|
||||
self.env['GITHUB_SHA'] = self.git('rev-parse', 'HEAD')
|
||||
self.env['VALIDATED_SHA'] = self.preprod
|
||||
self.mock('docker', """for arg in "$@"; do
|
||||
case "$arg" in
|
||||
*:validated-preprod-$VALIDATED_SHA) printf 'Digest: sha256:%064d\\n' 0; exit 0 ;;
|
||||
esac
|
||||
done
|
||||
exit 1
|
||||
""")
|
||||
|
||||
def git(self, *args):
|
||||
return subprocess.check_output(['git', *args], cwd=self.root, stderr=subprocess.PIPE,
|
||||
text=True).strip()
|
||||
|
||||
def commit(self, text):
|
||||
(self.root / 'source').write_text(text)
|
||||
self.git('add', 'source')
|
||||
self.git('commit', '-q', '-m', text)
|
||||
|
||||
def mock(self, name, body):
|
||||
path = self.bin / name
|
||||
path.write_text('#!/bin/sh\n' + body)
|
||||
path.chmod(0o755)
|
||||
|
||||
def resolve(self):
|
||||
return subprocess.run(['bash', str(SCRIPTS / 'resolve-release.sh')], cwd=self.root,
|
||||
env=self.env, capture_output=True, text=True)
|
||||
|
||||
def test_normal_merge_promotes_second_parent(self):
|
||||
result = self.resolve()
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn(f'commit={self.preprod}', (self.root / 'output').read_text())
|
||||
|
||||
def test_same_sha_promotion(self):
|
||||
self.env['GITHUB_SHA'] = self.preprod
|
||||
self.assertEqual(self.resolve().returncode, 0)
|
||||
|
||||
def test_explicit_short_sha(self):
|
||||
self.env['REQUESTED_SHA'] = self.preprod[:7]
|
||||
self.assertEqual(self.resolve().returncode, 0)
|
||||
|
||||
def test_modified_production_tree_blocks_release(self):
|
||||
self.commit('untested change')
|
||||
self.env['GITHUB_SHA'] = self.git('rev-parse', 'HEAD')
|
||||
self.env['REQUESTED_SHA'] = self.preprod
|
||||
self.assertNotEqual(self.resolve().returncode, 0)
|
||||
|
||||
def test_missing_preprod_success_blocks_release(self):
|
||||
self.env['VALIDATED_SHA'] = '0' * 40
|
||||
self.assertNotEqual(self.resolve().returncode, 0)
|
||||
|
||||
def test_registry_failure_blocks_release(self):
|
||||
self.mock('docker', 'exit 2\n')
|
||||
self.assertNotEqual(self.resolve().returncode, 0)
|
||||
|
||||
def test_missing_exporter_marker_blocks_release(self):
|
||||
self.mock('docker', """case "$*" in
|
||||
*xpeditis-log-exporter*) exit 1 ;;
|
||||
*) printf 'Digest: sha256:%064d\\n' 0 ;;
|
||||
esac
|
||||
""")
|
||||
self.assertNotEqual(self.resolve().returncode, 0)
|
||||
|
||||
def test_invalid_digest_blocks_release(self):
|
||||
self.mock('docker', "printf 'Digest: invalid\\n'\n")
|
||||
self.assertNotEqual(self.resolve().returncode, 0)
|
||||
|
||||
def test_release_exports_pinned_digests(self):
|
||||
self.assertEqual(self.resolve().returncode, 0)
|
||||
outputs = (self.root / 'output').read_text()
|
||||
self.assertIn('backend_digest=sha256:' + '0' * 64, outputs)
|
||||
self.assertIn('log_exporter_digest=sha256:' + '0' * 64, outputs)
|
||||
|
||||
def test_input_is_never_executed(self):
|
||||
self.env['REQUESTED_SHA'] = '$(touch injected)'
|
||||
self.assertNotEqual(self.resolve().returncode, 0)
|
||||
self.assertFalse((self.root / 'injected').exists())
|
||||
|
||||
def test_other_branch_cannot_deploy(self):
|
||||
self.env['GITHUB_REF'] = 'refs/heads/dev'
|
||||
self.assertNotEqual(self.resolve().returncode, 0)
|
||||
|
||||
def health(self, response, url='https://example.invalid/health'):
|
||||
self.mock('curl', response)
|
||||
self.mock('sleep', 'exit 0\n')
|
||||
return subprocess.run(['bash', str(SCRIPTS / 'health-check.sh'), url],
|
||||
env=self.env, capture_output=True, text=True)
|
||||
|
||||
def test_healthy_service(self):
|
||||
self.assertEqual(self.health('printf 200\n').returncode, 0)
|
||||
|
||||
def test_unhealthy_service_blocks(self):
|
||||
result = self.health('printf 503\n')
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('attempt 12', result.stdout)
|
||||
|
||||
def test_network_failure_blocks(self):
|
||||
self.assertNotEqual(self.health('exit 7\n').returncode, 0)
|
||||
|
||||
def test_missing_https_blocks(self):
|
||||
self.assertNotEqual(self.health('printf 200\n', '/api/v1/health').returncode, 0)
|
||||
|
||||
|
||||
class AuditChecks(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
self.bin = self.root / 'bin'
|
||||
self.bin.mkdir()
|
||||
for project in ['backend', 'frontend', 'log-exporter']:
|
||||
(self.root / 'apps' / project).mkdir(parents=True)
|
||||
self.env = dict(os.environ, PATH=f'{self.bin}:{os.environ["PATH"]}',
|
||||
RUNNER_TEMP=str(self.root), AUDIT_EXIT='0', SCAN_EXIT='0')
|
||||
self.mock('timeout', 'shift\nexec "$@"\n')
|
||||
self.mock('npm', """printf '%s' '{\"metadata\":{}}'
|
||||
exit \"$AUDIT_EXIT\"
|
||||
""")
|
||||
self.mock('git', 'tar -cf - -T /dev/null\n')
|
||||
self.mock('trivy', """while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = --output ]; then shift; output="$1"; fi
|
||||
shift
|
||||
done
|
||||
printf '%s' '{"Results":[{"Secrets":[{"RuleID":"fixture","Match":"synthetic-value","Code":{"Lines":[]}}]}]}' > "$output"
|
||||
exit "$SCAN_EXIT"
|
||||
""")
|
||||
|
||||
def mock(self, name, body):
|
||||
path = self.bin / name
|
||||
path.write_text('#!/bin/sh\n' + body)
|
||||
path.chmod(0o755)
|
||||
|
||||
def audit(self):
|
||||
return subprocess.run(['bash', str(SCRIPTS / 'security-audit.sh')],
|
||||
cwd=self.root, env=self.env, capture_output=True, text=True)
|
||||
|
||||
def test_success_and_secret_redaction(self):
|
||||
self.assertEqual(self.audit().returncode, 0)
|
||||
report = (self.root / 'security-reports' / 'source-security.json').read_text()
|
||||
self.assertNotIn('synthetic-value', report)
|
||||
self.assertNotIn('Code', report)
|
||||
self.assertIn('fixture', report)
|
||||
|
||||
def test_dependency_failure_is_not_masked_by_successful_source_scan(self):
|
||||
self.env['AUDIT_EXIT'] = '1'
|
||||
self.assertNotEqual(self.audit().returncode, 0)
|
||||
self.assertEqual(len(list((self.root / 'security-reports').glob('npm-audit-*.json'))), 4)
|
||||
|
||||
def test_source_failure_is_not_masked_by_successful_dependency_audits(self):
|
||||
self.env['SCAN_EXIT'] = '1'
|
||||
self.assertNotEqual(self.audit().returncode, 0)
|
||||
|
||||
def test_timeout_fails_closed(self):
|
||||
self.env['AUDIT_EXIT'] = '124'
|
||||
self.assertNotEqual(self.audit().returncode, 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
11
scripts/ci/validate-workflows.sh
Normal file
11
scripts/ci/validate-workflows.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
validator="${ACTIONLINT_BIN:-actionlint}"
|
||||
# Actionlint parses GitHub syntax. Normalize only Gitea's absolute action URLs;
|
||||
# this is static validation, not an act_runner execution test.
|
||||
for workflow in .gitea/workflows/*.yml; do
|
||||
sed 's#uses: https://github.com/#uses: #' "$workflow" |
|
||||
"$validator" -config-file .gitea/actionlint.yaml -shellcheck='' -stdin-filename "$workflow" -
|
||||
done
|
||||
bash -n scripts/ci/*.sh
|
||||
python3 scripts/ci/test_release_checks.py
|
||||
Loading…
Reference in New Issue
Block a user